当前位置: 首页 > article >正文

【Linux】<互斥量>解决<抢票问题>——【多线程竞争问题】

前言

大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁
主要内容含:
在这里插入图片描述

欢迎订阅 YY滴C++专栏!更多干货持续更新!以下是传送门!

  • YY的《C++》专栏
  • YY的《C++11》专栏
  • YY的《Linux》专栏
  • YY的《数据结构》专栏
  • YY的《C语言基础》专栏
  • YY的《初学者易错点》专栏
  • YY的《小小知识点》专栏
  • YY的《单片机期末速过》专栏
  • YY的《C++期末速过》专栏
  • YY的《单片机》专栏
  • YY的《STM32》专栏
  • YY的《数据库》专栏
  • YY的《数据库原理》专栏

目录

  • 一.抢票问题展示——"票数变成负数"
    • 1.问题展示:
    • 2.“ticket--”执行的过程是''非原子的'':多个线程会进入该代码段
  • 二.互斥&临界区&临界资源
  • 三.互斥量(锁)
    • 1.互斥量所需的头文件
    • 2.互斥量的初始化(动态&静态)
    • 3.互斥量的销毁
    • 4.互斥量的加锁&解锁
  • 四.<互斥量>解决<抢票问题>

一.抢票问题展示——“票数变成负数”

1.问题展示:

  • 下面代码所示
  • 我们会发现票数逐渐减少,最后甚至 减成了负数
  • 但是明明我们route函数里面设置的if ( ticket > 0 )后面才会ticket--,这是为什么?
  • 原因: ticket–的操作是 非原子性的,会被调度机制打断, 有多个线程会进入该代码段
  • 关于原子性,下面有详细分析
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int ticket = 100;//设置的剩余票数

void *route(void *arg)
{
     char *id = (char*)arg;
     while ( 1 ) {
          if ( ticket > 0 ) {
          usleep(1000);
          printf("%s sells ticket:%d\n", id, ticket);
          ticket--;
     } 
     else {
          break;
          }
     }
}
int main( void )
{
     pthread_t t1, t2, t3, t4;
     pthread_create(&t1, NULL, route, "thread 1");
     pthread_create(&t2, NULL, route, "thread 2");
     pthread_create(&t3, NULL, route, "thread 3");
     pthread_create(&t4, NULL, route, "thread 4");
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
     pthread_join(t3, NULL);
     pthread_join(t4, NULL);
}

--一次执行结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1//减成负数
thread 3 sells ticket:-2

2.“ticket–”执行的过程是’‘非原子的’':多个线程会进入该代码段

  • 原子性:
  • 原子性: 不会被任何调度机制打断的操作
  • 该操作只有两态,要么 完成 ,要么 未完成
  • -- 操作并不是原子操作,而是对应 三条汇编指令
  1. load :将共享变量ticket从内存加载到寄存器中
  2. update : 更新寄存器里面的值,执行-1操作
  3. store :将新值,从寄存器写回共享变量ticket的内存地址
  • 票数变成负数原因分析 / ticket–分析:
  1. if 语句判断条件为真以后,代码可以并发的切换到其他线程
  2. usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中, 可能有很多个线程会进入该代码段
  3. –ticket 操作本身就不是一个原子操作

二.互斥&临界区&临界资源

通过上述问题,我们明白:

  • 代码 必须要有互斥行为 当代码进入临界区执行时,不允许其他线程进入该临界区。

  • 互斥:在任何时刻,互斥保证有且只有一个执行流进入临界区访问临界资源,通常对临界资源起保护作用

  • 能实现该 互斥行为 的,也就是我们下面用到的,即 互斥量

  • 如果多个线程同时要求执行临界区的代码, 任何一个时刻, 也只允许一个线程正在访问共享资源

  • 我们把我们进程中访问临界资源的代码片段,称为 临界区
    在这里插入图片描述

  • 对应上文提到抢票问题,我们也明确了共享区,以及该加锁解锁的位置,如下图所示:
    在这里插入图片描述

三.互斥量(锁)

1.互斥量所需的头文件

  • 线程库中有互斥锁
	#include <pthread.h> 
	#include <stdio.h>  

2.互斥量的初始化(动态&静态)

初始化互斥量有两种方法:静态初始化和动态初始化

  • 方法1,静态初始化:
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁!!!
    静态初始化的互斥量不需要显式调用pthread_mutex_destroy函数进行销毁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
	#include <pthread.h> 
	#include <stdio.h>  
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  

	void* thread_func(void* arg) {  
	    pthread_mutex_lock(&mutex); // 访问共享资源  
	    // ... 执行一些操作 ...  
	    pthread_mutex_unlock(&mutex); // 释放互斥量  
	    return NULL;  
	}  
	int main() {  
	    pthread_t thread1, thread2;  
	    // 创建两个线程  
	    pthread_create(&thread1, NULL, thread_func, NULL);  
	    pthread_create(&thread2, NULL, thread_func, NULL);  
	    // 等待两个线程结束  
	    pthread_join(thread1, NULL);  
	    pthread_join(thread2, NULL);  
	    // 注意:静态初始化的互斥量不需要显式销毁  
	    return 0;  
	}
  • 方法2,动态初始化
  • 动态初始化的互斥量在使用完毕后需要显式调用pthread_mutex_destroy函数进行销毁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
    mutex:要初始化的互斥量
    attr:指向互斥量属性对象的指针。如果传递NULL,则使用默认的互斥量属性(通常是非递归、非错误检查的)
	#include <pthread.h> 
	#include <stdio.h>  
	// 互斥量  
	pthread_mutex_t mutex; 

	void* thread_func(void* arg) {  
	    pthread_mutex_lock(&mutex); // 访问共享资源  
	    // ... 执行一些操作 ...  
	    pthread_mutex_unlock(&mutex); // 释放互斥量  
	    return NULL;  
	}  
	int main() {  
	    pthread_t thread1, thread2;  
	    // 动态初始化互斥量 
        pthread_mutex_init(&mutex, NULL); 
	    // 创建两个线程  
	    pthread_create(&thread1, NULL, thread_func, NULL);  
	    pthread_create(&thread2, NULL, thread_func, NULL);  
	    // 等待两个线程结束  
	    pthread_join(thread1, NULL);  
	    pthread_join(thread2, NULL);  
	    // 注意:动态初始化的互斥量需要显式销毁  
        // 销毁互斥量  
        pthread_mutex_destroy(&mutex); 
	    return 0;  
	}

3.互斥量的销毁

  • 销毁互斥量要注意:
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
  • 销毁互斥量语法:
int pthread_mutex_destroy(pthread_mutex_t *mutex)

4.互斥量的加锁&解锁

  • 互斥量的加锁&解锁语法:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:
    成功返回0,失败返回错误号

四.<互斥量>解决<抢票问题>

  • 现在明确了 共享区与要加锁的位置 ,也清楚了 锁(互斥量)的语法
  • 改进原来的售票系统:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>

int ticket = 100;

pthread_mutex_t mutex;//定义全局锁

void *route(void *arg)
{
     char *id = (char*)arg;
     while ( 1 ) {
          pthread_mutex_lock(&mutex);//进入共享区前上锁
     if ( ticket > 0 ) {
          usleep(1000);
          printf("%s sells ticket:%d\n", id, ticket);
          ticket--;
          pthread_mutex_unlock(&mutex);//退出共享区时解锁
          } 
     else {
          pthread_mutex_unlock(&mutex);//退出共享区时解锁
     break;
           }
     }
}

int main( void )
{
     pthread_t t1, t2, t3, t4;
     
     pthread_mutex_init(&mutex, NULL);//锁的初始化
     
     pthread_create(&t1, NULL, route, "thread 1");
     pthread_create(&t2, NULL, route, "thread 2");
     pthread_create(&t3, NULL, route, "thread 3");
     pthread_create(&t4, NULL, route, "thread 4");
     
     pthread_join(t1, NULL);
     pthread_join(t2, NULL);
     pthread_join(t3, NULL);
     pthread_join(t4, NULL);
     
     pthread_mutex_destroy(&mutex);//销毁锁
}

http://www.kler.cn/news/354810.html

相关文章:

  • 小程序底部导航按钮实现
  • 执行vue create XXX报错The operation was rejected by your operating system
  • 计算机网络day2
  • matlab的resample函数
  • spring 注解
  • IRP读写函数
  • 八股面试3(自用)
  • 机器学习与神经网络:物理学的新边疆
  • docker 复制文件,清除不再使用数据导出以及导出文件系统
  • 搜维尔科技:力反馈遥操作解决方案,五指灵巧手遥操作解决方案
  • Java初学者的学习顺序
  • 网络基础知识:六大交换机关键知识解析
  • 无人机之遥感影像处理篇
  • 国产 HDMI 发送芯片,兼容 HDMI1.4b 及 HDMI 1.4b 下的视频 3D 传输格式。
  • JavaScript 第9章:面向对象编程
  • 虎牙Android面试题及参考答案
  • C++ 方法积累
  • 【优选算法】(第三十六篇)
  • 【实战案例】Nacos从安装到服务注册发现再到配置中心(附常见问题解决方案)
  • 前端开发设计模式——状态模式