首页 > 系统相关 >【Linux】<互斥量>解决<抢票问题>——【多线程竞争问题】

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

时间:2024-10-17 11:17:17浏览次数:8  
标签:NULL thread 抢票 互斥 mutex pthread ticket 多线程

前言

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

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

目录

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

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.互斥量的初始化(动态&静态)

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

  • 方法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;  
	}

2.互斥量的销毁

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

3.互斥量的加锁&解锁

  • 互斥量的加锁&解锁语法:
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);//销毁锁
}

标签:NULL,thread,抢票,互斥,mutex,pthread,ticket,多线程
From: https://blog.csdn.net/YYDsis/article/details/142934452

相关文章

  • 【Linux】解锁线程基本概念和线程控制,步入多线程学习的大门
    目录1、线程初识1.1线程的概念1.2.关于线程和进程的进一步理解1.3.线程的设计理念1.4.进程vs线程(图解)1.5地址空间的第四谈2.线程的控制:2.1.关于线程控制的前置知识2.2创建线程的系统调用:这个几号手册具体代表的什么含义?2.3.线程终止我们怎么没有像进程一样获取线程......
  • OS-Lab4-多线程编程基础
    实验目的事先编辑好数据文件1.dat和2.dat,它们的内容分别为12345678910和-1-2-3-4-5-6-7-8-9-10。根据示例代码和其运行效果设计一个程序,在这个程序中一共有3个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另外一个线程从缓冲区读取数据作不同的处理(加和......
  • 多线程&JUC的学习
    1、什么是线程?进程:进程是程序的基本执行实体。一个软件运行之后就是一个进程。线程:是操作系统能够运行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解:应用软件中互相独立,可以同时运行的功能。2、多线程的作用?        提高效率。3、多线......
  • 火山引擎数智平台 VeDI:A/B 实验互斥域流量分配体系上线
    近日,火山引擎A/B测试平台(DataTester)完成了一次重要升级,推出互斥域流量分配体系,这一功能意味着企业在产品优化策略上有新的突破空间。此次升级的核心亮点是允许企业根据实际需求,灵活地将用户流量分割成多个独立的区块,每个区块都能独立开展一项或多项实验,打破了传统A/B测试中......
  • C++互斥锁
    互斥锁(Mutex,全称:MutualExclusion)是一种用于多线程编程中的同步机制,用来确保在同一时刻只有一个线程可以访问共享资源。它通过锁定机制防止多个线程同时对共享资源进行读写操作,从而避免数据竞争和不一致性问题。互斥锁的核心思想是保证互斥访问,即当一个线程持有互斥锁并正在访问......
  • 【多线程奇妙屋】“线程等待” 专讲,可不要只会 join 来线程等待哦, 建议收藏 ~~~
    本篇会加入个人的所谓鱼式疯言❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言而是理解过并总结出来通俗易懂的大白话,小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.......
  • Java中多线程的学习
    Java多线程学习总结目录Java多线程学习总结什么是进程什么是线程进程与线程的区别地址空间资源占用健壮性执行过程并发与资源消耗创建线程方式一:继承Thread类,并重写run()方法方式二:实现Runnable接口,并实现run()方法线程的状态线程暂停执行条件线程优先级多线程多线......
  • java爬虫多线程代理:为数据采集提供强力支持
    Java爬虫中的多线程与代理应用在当今这个信息爆炸的时代,数据的获取与处理变得尤为重要。Java作为一种强大的编程语言,广泛应用于爬虫开发中。而在爬虫的实践中,多线程和代理的结合,可以让我们的爬虫如同一支训练有素的队伍,快速、高效地获取所需数据。接下来,我们将深入探讨Java爬......
  • 多线程批量插入数据
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、多线程使用背景二、代码实现1.单线程插入2.多线程插入总结前言在面试过程中我们经常会被问到多线程的问题。但是在实际工作过程中可能使用的场景不是特别多,在这边给大家提供一个多......
  • 单线程与多线程爬虫
    单线程爬虫在执行爬取任务时,程序一次只处理一个任务。这意味着在一个时刻,它只能向一个服务器发送请求,并等待该请求的响应。完成这个请求后,它才会发送下一个请求。单线程爬虫的结构简单,易于理解和实现,但效率较低,特别是在网络延迟较大或需要处理大量数据时。爬虫多为IO密集型程序,......