首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux】<互斥量>解决<抢票问题>——【多线程竞争问题】

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

作者头像
YY的秘密代码小屋
发布2024-10-18 09:29:53
发布2024-10-18 09:29:53
21600
代码可运行
举报
文章被收录于专栏:C++系列C++系列
运行总次数:0
代码可运行

前言 大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁 主要内容含:

在这里插入图片描述
在这里插入图片描述

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

1.问题展示:

  • 下面代码所示
  • 我们会发现票数逐渐减少,最后甚至 减成了负数
  • 但是明明我们route函数里面设置的if ( ticket > 0 )后面才会ticket--,这是为什么?
  • 原因: ticket–的操作是 非原子性的,会被调度机制打断, 有多个线程会进入该代码段
  • 关于原子性,下面有详细分析
代码语言:javascript
代码运行次数:0
运行
复制
// 操作共享变量会有问题的售票系统代码
#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.互斥量所需的头文件

  • 线程库中有互斥锁
代码语言:javascript
代码运行次数:0
运行
复制
	#include <pthread.h> 
	#include <stdio.h>  

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

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

  • 方法1,静态初始化:
  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁!!! 静态初始化的互斥量不需要显式调用pthread_mutex_destroy函数进行销毁
代码语言:javascript
代码运行次数:0
运行
复制
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
代码语言:javascript
代码运行次数:0
运行
复制
	#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函数进行销毁
代码语言:javascript
代码运行次数:0
运行
复制
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
    mutex:要初始化的互斥量
    attr:指向互斥量属性对象的指针。如果传递NULL,则使用默认的互斥量属性(通常是非递归、非错误检查的)
代码语言:javascript
代码运行次数:0
运行
复制
	#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 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
  • 销毁互斥量语法:
代码语言:javascript
代码运行次数:0
运行
复制
int pthread_mutex_destroy(pthread_mutex_t *mutex);

4.互斥量的加锁&解锁

  • 互斥量的加锁&解锁语法:
代码语言:javascript
代码运行次数:0
运行
复制
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:
    成功返回0,失败返回错误号

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

  • 现在明确了 共享区与要加锁的位置 ,也清楚了 锁(互斥量)的语法
  • 改进原来的售票系统:
代码语言:javascript
代码运行次数: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);//销毁锁
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.抢票问题展示——“票数变成负数”
    • 1.问题展示:
    • 2.“ticket–”执行的过程是’‘非原子的’':多个线程会进入该代码段
  • 二.互斥&临界区&临界资源
  • 三.互斥量(锁)
    • 1.互斥量所需的头文件
    • 2.互斥量的初始化(动态&静态)
    • 3.互斥量的销毁
    • 4.互斥量的加锁&解锁
  • 四.<互斥量>解决<抢票问题>
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档