多线程(线程互斥)

这篇具有很好参考价值的文章主要介绍了多线程(线程互斥)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

抢票代码编写

学习了前面有关线程库的操作后,我们就可以模拟抢票的过程
假设我们创建四个线程,分别代表我们的用户
然后设定总票数为1000张,四个线程分别将进行循环抢票操作,其实就是循环对票数进行打印,并进行对应的减减操作
一旦票数为0,也就是票没有了,我们就让线程从循环中退出
当然,我们知道抢票,和抢到票后付费等等操作,都是需要时间的
所以我们每次抢票的时候,加上相应的延时函数usleep,它的功能和sleep函数一样,不过是micro微秒级别的,而sleep里的参数是秒.
多线程(线程互斥),Linux,linux
具体的代码实现如下:

1 #include <iostream>
  2 #include <pthread.h>
  3 #include <cstring>
  4 #include <unistd.h>
  5 using namespace std;
  6 int tickets = 10000;
  7 void* BuyTickets(void* args)
  8 {
  9   std::string name = static_cast<const char*>(args);
 10   while (true)
 11   { 
 12     if(tickets > 0)
 13     {
 14       usleep(2000); //模拟抢票需要的时间
 15       cout << name << " ticket numbers: " << tickets-- << endl;
 16     }
 17     else
 18     {                                                                                                                                                               
 19       break;
 20     }
 21     //模拟抢到票的后续操作
 22     usleep(1000);
 23   }
 24 
 25   return nullptr;
 26 }
 27 int main()
 28 {
 29   pthread_t tids[4];
 30   int n = sizeof(tids)/sizeof(tids[0]);
 31   for (int i = 0;i < n;++i)
 32   {
 33     char* name = new char[64];
 34     snprintf(name,64,"thread-%d",i+1);
 35     pthread_create(tids + i,nullptr,BuyTickets,(void*)name);
 36   }
 37 
 38   for(int i = 0;i < n;++i)
 39   {
 40     pthread_join(tids[i],nullptr);
 41   }
 42   return 0;
 43 }

按照我们的预期来说,每个线程都会抢票,当票数抢到0的时候,每个线程都会自动退出循环,停止抢票
但显示打印出来的结果却非常奇怪
可以看到,线程1,3,在tickets数目已经小于等于0的情况下,仍然进去了循环,这是为什么呢?
多线程(线程互斥),Linux,linux

问题分析

我们前面提到过每个线程共享的是同一个虚拟地址空间
这也就意味着,**有些资源是每个线程都共享的!**最基本的,比如我们所说的代码段Text Segment,数据段Data Segment都是共享的

一般来说,线程共享的资源有下面几种:

  1. 全局变量
  2. 文件描述符表
  3. 每种信号的处理方式(SIG_ IGN、SIG_DFL或者自定义的信号处理函数)
  4. 当前工作目录
  5. 用户id和组id

可以看到,我们上述的tickets变量就是一个全局变量,是被所有执行流所共享的!这也是我们模拟实现抢票代码的基础
但是上面的结果已经指出,线程中大部分资源直接共享或间接共享,就可能导致我们的并发问题
对于我们编写的一条简单的自增C++代码语句
实际在底层转成汇编代码后,会被转成三条汇编语句进行实现
多线程(线程互斥),Linux,linux

第一行汇编语句,我们要先将数据从内存中load到我们的寄存器中,一般是eax
第二行汇编语句,我们要对eax里面load的数据进行相应的加减操作
第三行汇编语句,将寄存器里面的内容,放回到我们数据所在内存的位置

一切看似合情合理,因为只有CPU里面的寄存器配合ALU才有运算能力,不然假如内存可以直接对变量进行加减操作,那我还要CPU干什么?中间商赚差价吗?
但是问题的出现,也正是这个原因
线程的切换,是随时都有可能发生的
假如存在两个线程A,B,当线程A执行的时候,刚好把tickets减为0,运行到对应的第二行代码,突然操作系统OS大哥说:“你工作时间到了,该要线程B工作了!”,线程A只能带着它的上下文和对应减为0的tickets变量,灰溜溜的走了
但是,注意此时线程A有执行第三行汇编代码吗?
没有!线程B眼中的数据tickets,还是等于1
这就意味着线程B对于if语句的判断,依旧是成立的!
所以线程B仍然会进来
但是操作系统OS大哥又有点不太满意了,说:“线程B你的动作太慢了,还是先让线程A把剩下的活先干完吧,等等再分时间给你”
于是线程A就把tickets == 0的数据加载到内存中(继续运行第三行汇编代码)
那此时再切换回线程B的话,在线程B的眼里,tickets此时等于什么呢?
答案是0
但是我已经过了if那条判断了,因此放到寄存器里进行加减操作,会得到-1!这就是-1的由来

抽离概念

解决一个问题的前提,是先描述准确这个问题
而描述问题,无法避免的就是要引入一些概念和定义
我们把上述不同线程看到的同一份共享资源,我们称作为临界资源
临界资源在任何时刻,都只允许一个执行流进行访问
而访问临界资源的这部分代码,我们称之为临界区;反之不访问的话,我们就称作为非临界区
最后我们在定义一个概念,我们称之为原子性
原子性指的是不可被分割的操作,该操作不会被任何调度机制打断,该操作只有两态,要么完成,要么未完成;就像我们高中老师经常说的一句话,你可以不做,要做,就一定要完成
有了这三个概念,我们就可以准确的对上面的问题进行描述了
1.上述的问题,只会发生在临界区中,非临界区中,并不存在访问临界资源的概念
2.问题的出现,正是由于我们所看的一句代码,在底层汇编中,等价于三行代码语句,并不是原子性的!
多线程(线程互斥),Linux,linux

解决问题

锁的引入

描述完问题后,我们就要着手解决这个问题
而在linux操作系统中,大佬早就给我们想好解决方案
答案就是加锁
在原生线程库中,已经设计好一种名为**锁(互斥量)**的结构,专门用来解决类似问题
多线程(线程互斥),Linux,linux
其中上述两个函数是搭配使用,初始化init,和销毁destroy
(没错和指针类似操作,有创建,用完后,记得及时销毁)
而如果锁是一个静态或者全局变量,按下面的方式进行初始化,则不用销毁,操作系统OS会帮你自动销毁
只要在对应的临界区加锁,解锁,我们就可以解决多线程并发的问题
对应的加锁,解锁函数,分别叫做
pthread_mutex_lock()与pthread_mutex_unlock()

PS:互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁

参数都只有一个,就是指向我们锁的指针
多线程(线程互斥),Linux,linux
下面,我们简单来编写一段代码体验一下加锁操作

  1 #include <iostream>
  2 #include <pthread.h>
  3 #include <cstring>
  4 #include <unistd.h>
  5 using namespace std;
  6 int tickets = 1000;
  7 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  8 void* BuyTickets(void* args)
  9 {
 10    std::string name = static_cast<const char*>(args);
 11    while (true)
 12    {  
 13       pthread_mutex_lock(&mutex);  //临界区加锁
 14       if(tickets > 0)
 15       {
 16         usleep(2000); //模拟抢票需要的时间
 17         cout << name << " ticket numbers: " << tickets-- << endl;
 18         pthread_mutex_unlock(&mutex); //临界区结束及时解锁
 19       }
 20       else
 21       {
 22         pthread_mutex_unlock(&mutex); //在循环结束break时,也要记得解锁
 23         break;
 24       }                                                                                                                                                             
 25       //模拟抢到票的后续操作
 26       usleep(1000);
 27     }
 28 
 29   return nullptr;
 30 }
 31 int main()
 32 {
 33     pthread_t tids[4];
 34     int n = sizeof(tids)/sizeof(tids[0]);
 35     for (int i = 0;i < n;++i)
 36     {
 37       char* name = new char[64];
 38       snprintf(name,64,"thread-%d",i+1);
 39       pthread_create(tids + i,nullptr,BuyTickets,(void*)name);
 40     }
 41     for (int i = 0;i < n;++i)
 42     {
 43       pthread_join(tids[i],nullptr);
 44     }
 45    return 0;
 46 }
 47

可以看到加锁后,结果就完美符合我们的预期了
票数不会再出现减到-1,-2的情况
多线程(线程互斥),Linux,linux

改造锁代码

但是上面的写法,显然非常简单
用C++代码实现,那我们肯定也要试一下封装来实现锁
我们构建一个TData类,其中里面包括线程的名字,还有对应的锁指针

class TData
{
public:
 TData(const string& name,pthread_mutex_t*mutex):_name(name),_pmutex(mutex)
 {}
 ~TData()
 {}
public:
 string _name; //线程对应的名字
 pthread_mutex_t* _pmutex;
};

则上述代码可以改造成这样
这里我们锁并没有设成全局变量或静态变量,而是采用了第一种方式创建,调用init,destroy函数

  1 #include <iostream>
  2 #include <pthread.h>
  3 #include <cstring>
  4 #include <unistd.h>
  5 using namespace std;
  6 int tickets = 1000;
  7 //pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  8 class TData
  9 {
 10 public:
 11   TData(const string& name,pthread_mutex_t* mutex):_name(name),_pmutex(mutex)
 12   {}
 13   ~TData()
 14   {}
 15 public:
 16   string _name; //线程对应的名字
 17   pthread_mutex_t* _pmutex;
 18 };
 19 void* BuyTickets(void* args)
 20 {
 21    TData* td = static_cast<TData*>(args);
 22    while (true)
 23    {
 24       pthread_mutex_lock(td->_pmutex);
 25       if(tickets > 0)
 26       {
 27         usleep(2000); //模拟抢票需要的时间
 28         cout << td->_name << " ticket numbers: " << tickets-- << endl;
 29         pthread_mutex_unlock(td->_pmutex);
 30       }                                                                                                                                                             
 31       else
 32       {
 33         pthread_mutex_unlock(td->_pmutex); 
 34         break;
 35       }
 36       //模拟抢到票的后续操作                                                                                                                                        
 37       usleep(1000);
 38     }
 39   
 40   return nullptr;
 41 }
 42 int main()
 43 {
 44     pthread_t tids[4];
 45     pthread_mutex_t mutex;
 46     pthread_mutex_init(&mutex,nullptr); //对锁进行初始化,第二个参数为所得属性,一般设为nullptr
 47     int n = sizeof(tids)/sizeof(tids[0]);
 48     for (int i = 0;i < n;++i)
 49     {
 50       char* name = new char[64];
 51       snprintf(name,64,"thread-%d",i+1);
 52       TData* td = new TData(name,&mutex);
 53       pthread_create(tids + i,nullptr,BuyTickets,td);
 54     }
 55     for (int i = 0;i < n;++i)
 56     {
 57       pthread_join(tids[i],nullptr);
 58     }
 59    pthread_mutex_unlock(&mutex); //销毁锁
 60    return 0;
 61 }

可以看到结果和我们的预期说一样的,和之前也是相同的
多线程(线程互斥),Linux,linux

为什么叫锁呢?

回答这个问题其实很简单,像我们学校总有一些教室
我们称之为公共资源
一间教室,一次肯定只能够一个社团举办活动(除了几个社团联合举办活动等特殊情况外)
但是我们如何能够做到一间教室只能够一个社团使用呢?
假如其他人闯进来强行霸占呢?
这个时候,就需要给教室的门加一把锁
我使用教室的时候(访问临界资源时),别人能够从门里面进来吗?
答案是不能,这也就解决了多个社团(线程)访问同一间教室(临界资源),造成并发问题出现的可能

细节剖析

1.凡是访问同一个临界资源的线程,都要进行加锁保护,并且这一把锁要是同一把锁,这是一个游戏规则,不能有例外! 你不能说同时上两把不一样的锁;或者有部分线程没上锁,一部分线程上锁等情况出现
2.每一个线程访问临界区时都得加锁,加锁后,能够使原本并行执行的代码,转变为串行执行 但这也同样意味着效率下降,因此,我们可以看到运行时间明显提高了不少 串行化的代价无法解决,但可以减弱
那就是加锁的粒度尽量细一点,我们加锁的时候,只给对应的代码加锁,临界区不需要很大!

那临界区可不可以是一行代码呢?答案是可以的!
临界区可以是一行代码,也可以是一批代码,取决于我们哪部分代码访问了临界资源
还有一个常见的误区,在加锁后,线程可以被切换吗?
很多人可能都会回答不可以,而答案恰恰相反,是可以被切换的!
对于加锁和解锁,我们并不需要特殊化它们,在计算机眼里,它们也仅仅是一批普通代码
这就类似于我们大学里面可能会有一个人的VIP自习室
一次只能供一个人预约,一旦有人预约了,只有他自己从系统选择退出,才能有新的人预约,是一个道理
预约了自习室的人,可以没有在自习室里面自习,去吃饭了(没有工作,此时其它线程被OS调度)
但是没有影响!!!其它人进不去自习室里面,因为系统上还显示我预约占领着自习室
这也正体现互斥带来串行化的表现,站在其它线程角度,只有两种状态,锁被我申请了(持有锁),锁被我释放了(不持有锁)

锁的原理

于是就有人质疑了
你说不同线程看到的都是同一把锁,也就意味着锁本身就是公共资源
那锁如何保证自己的安全呢?为什么加锁就能解决并发问题呢?
关键就在于我们前面提到过的原子性
加锁和解锁这个动作都是原子性的,它可和我们的加减操作不同,也就是,只要进行加锁操作,谁都无法打断我,我一定会成功完成!否则就是失败,不存在苟且偷生(做一半),只有破釜沉舟

预备知识1

在大多数体系结构都提供了swap或exchange(XCHG)指令,拿XCHG指令来说,它相当于MOV指令的简化版,但它其中有一个强大的功能,就是把寄存器和内存单元的数据相交换
这是一条指令,换句话说,这条指令基础保证了我们原子性的实现的可能

预备知识2

我们需要意识到寄存器硬件只有一套的,用于临时存储和操作数据,以便在指令级别上执行各种操作
但我们现在是多个线程
这也就意味着这一套寄存器,必定由多个线程所共用
但寄存器又如何区分这多个线程呢?
答案就是每个线程都有自己的寄存器上下文.
当操作系统进行线程上下文切换时,它会保存当前线程的寄存器上下文,然后加载下一个线程的寄存器上下文.
这就意味着每个线程可以独立地使用一组寄存器来执行其指令和操作数据,而不会与其他线程干扰.
这种隔离保证了线程之间的相互独立性
但是寄存器内部的数据,是每一个线程都有的!
就好比图书馆的课桌还有电脑插头,这些都是只有一套的(寄存器只有一套)
但是我们每个人使用它的时候,放置的书本,水壶等等,往往是不同的
(线程之间具有相互独立性)
但是假如有一天在图书馆的课桌上放了一张纸,上面写道:“该课桌要维修,临时不能使用”,则我们每个线程想使用该桌子时,都会看到这个内容,然后自觉离去(寄存器里的内容是每个线程都有的)
换句话说,寄存器不能简单把它等同于寄存器的内容,它只是一个临时存储和操作数据的硬件,对于每一个线程而言,寄存器里的数据+线程自己独有的寄存器上下文,这才构成了线程所拥有的内容

原理讲解

因此,假如我们把mutex,这一把锁,简单看作1
在底层,lock的伪代码是这样实现的
多线程(线程互斥),Linux,linux
第一句指令将寄存器al里面的值清0,换言之,对于每个线程来说,每个线程执行这段代码,实际上就是向自己的上下文写0
第二句指令,就是将内存中的mutex与寄存器al里面的值进行交换(XCHG指令),并且该指令操作,是原子性的!只有一条代码
这句指令执行外后,会出现什么情况?
有且只有一个线程顺利得到锁,它上下文的内容会变成1
但是其它线程呢?
只有它上下文的0和内存里面的0进行互换
(不会新增任何的1,而1只会进行流转)
第三条指令判断al寄存器里的内容是否大于0,不是则被挂起
最后的结果也就显而易见了
即便当前有锁的线程被切走,但是其它线程你没有锁啊!对应我们之前的故事,就是没有预约VIP自习室啊!那就算校长来了都没有用,门不会给你打开

交换的本质: 将共享数据交换到自己私有上下文中

这就是加锁的原理,一个不让你通过的策略,来实现在临界区,由并发执行,转为串行执行

那解锁呢?
就是将mutex里面的内容置1,把预约取消,锁放回去的过程 那没有把al寄存器里的内容置0,会不会有什么影响?
反之加锁的第一步,又会全部清0,所以完全不用担心

demo版的线程封装

在了解互斥量后,我们可以尝试对线程进行封装
创建一个Thread.hpp文件

类内成员设计

设计一个类,首先我们设定类内成员是什么
既然是线程封装,那线程id肯定要有吧
那不同线程,肯定会有对应的线程名字,所以也可以加个string类型的name
然后每个线程,也会有对应的运行状态,因此还可以加一个status
在创建的时候,我们还可以先把指定的函数给定,因此还可以加上两个参数,func与args
其中func为线程运行的函数,而args则是一个空指针
多线程(线程互斥),Linux,linux
多线程(线程互斥),Linux,linux

构造与析构

我们先指定线程id初始化为0,初始运行状态为新线程
构造的时候,只要传对应线程的号码,用来初始化名字
还有传入对应线程运行的函数,以及对应的args参数即可
多线程(线程互斥),Linux,linux

类内方法

没有什么好说的,整体就是返回类内的属性,使得我们用户以后创建对象后,能够迅速调用对象的属性进行查看
多线程(线程互斥),Linux,linux

线程运行

线程运行,实际上就是进行真正意义上,线程的创建
也就是我们在我们的Run函数中要调用pthread_create函数
其中的第三个参数,是我们之前所提到过用户传进来的参数
但是,我们今天,不直接传进去_func与_args参数
而是换种方法,在类内部实现一个函数,让我们调用pthread_create函数时,调用该函数
多线程(线程互斥),Linux,linux
但是程序此时会发生报错
这是因为在类内部的函数,第一个参数实际上会隐含this指针,指向该对象
因此,调用该函数的时候,由于参数不匹配,pthread_create要求的函数的参数只能有void*.
所以我们把它设为静态static函数
但是这又会引发一个新的问题
虽然参数里面没有this指针了,但是静态函数,就不能再访问类内成员了!
这里提供一个解决办法:
调用pthread_create函数时,将对象的this指针传进来
这样运行的时候,只需要对this指针解引用,就可以得到该对象,那就可以访问对象里面的属性了
多线程(线程互斥),Linux,linux

线程等待

多线程(线程互斥),Linux,linux

整体代码

    1 #include <iostream>
    2 #include <stdlib.h>
    3 #include <pthread.h>
    4 #include <cstring>
    5 #include <string>
    6 class Thread{
    7 public:
    8   typedef enum
    9   {
   10      NEW = 0,
   11      RUNNING,
   12      EXITED
   13   }ThreadStatus;
   14     typedef void* (*func_t)(void*);
   15 public:
   16   Thread(int num,func_t func,void* args):_tid(0),_status(NEW),_func(func),_args(args)
   17   {
   18      //名字由于还要接收用户给的编号,因此在构造函数内进行初始化
   19      char buffer[128];                                                                                                                                            
   20      snprintf(buffer,sizeof(buffer),"thread-%d",num);
   21      _name = buffer;
   22   }
   23   ~Thread()
   24   {}
   25   //返回线程的状态
   26   int status()  {return _status;}
   27   //返回线程的名字
   28   std::string name() {return _name;}
   29   //返回线程的id
   30   //只有线程在运行的时候,才会有对应的线程id
   31   pthread_t GetTid()
   32   {
   33     if (_status == RUNNING)
   34     {
   35       return _tid;
   36     }                                                                                                                                                             
   37     else
   38     {
   39       return 0;
   40     }
   41   }
   42   //类成员函数具有默认参数this
   43   //但是会有新的问题
   44   static void * ThreadRun(void* args)
   45   {
   46     Thread* ts = (Thread*)args;  //此时就获取到我们对象的指针
   47     // _func(args);  //此时就无法回调相应的方法(成员函数无法直接被访问)
   48     (*ts)();
   49     return nullptr;
   50   }
   51   void operator()() //仿函数
   52   {
   53      //假如传进来的线程函数不为空,则调用相应的函数
   54      if(_func != nullptr)  _func(_args);
   55   }
   56   //线程运行
   57   void Run()
   58   {
   59     //线程创建的参数有四个
   60     //int n = pthread_create(&_tid,nullptr,_func,_args);
   61     int n = pthread_create(&_tid,nullptr,ThreadRun,this);
   62     if(n != 0)  exit(0);
   63     _status = RUNNING;
   64   }
   65 
   66   //线程等待
   67   void Join()
   68   {
   69     int n = pthread_join(_tid,nullptr);                                                                                                                           
   70     if (n != 0)
   71     {
   72        std::cerr << "main thread join error :" << _name << std::endl;
   73        return;
   74     }
   75     _status = EXITED;
   76   }
   77 private:
   78    pthread_t _tid;    //线程id
   79    std::string _name; //线程的名字
   80    func_t _func;       //未来要回调的函数
   81    void*_args;
   82    ThreadStatus _status; //目前该线程的状态
   83 };

代码测试

int main()
{
   Thread t1(1,threadRun,(void*)"Hello!");
   Thread t2(2,threadRun,(void*)"Hello!");
   cout << "thread name: " << t1.name() << " thread id: " << t1.GetTid() << " Thread status: "<< t1.status() << endl;
   cout << "thread name: " << t2.name() << " thread id: " << t2.GetTid() << " Thread status: "<< t2.status() << endl;

   t1.Run();
   t2.Run();
   cout << "thread name: " << t1.name() << " thread id: " << t1.GetTid() << " Thread status: "<< t1.status() << endl;
   cout << "thread name: " << t2.name() << " thread id: " << t2.GetTid() << " Thread status: "<< t2.status() << endl;
  
   t1.Join();
   t2.Join();
   cout << "thread name: " << t1.name() << " thread id: " << t1.GetTid() << " Thread status: "<< t1.status() << endl;
   cout << "thread name: " << t2.name() << " thread id: " << t2.GetTid() << " Thread status: "<< t2.status() << endl;                                              
   return 0;
}

运行结果如下:
多线程(线程互斥),Linux,linux
当成功实现线程封装,相信我们对C++多线程库的封装,也就有了更深一步的理解

demo版的锁的封装

前面我们提到过,创建一个锁,既要考虑lock,又要考虑unlock问题
那我们能不能封装锁,使其变成一个LockGuard类,能够自动加锁,解锁呢?
答案是可以的!
只要在其创建的时候,调用我们的封装的锁的Lock类方法
析构的时候,调用我们封装的锁的Unlock方法即可实现

  1 #pragma once
  2 
  3 #include <iostream>
  4 #include <pthread.h>
  5 
  6 class Mutex
  7 {
  8 public:
  9   Mutex(pthread_mutex_t* mutex):pmutex(mutex)
 10   {}
 11   ~Mutex()
 12   {}
 13   void Lock()
 14   {
 15      pthread_mutex_lock(pmutex);
 16   }
 17   void Unlock()
 18   {
 19     pthread_mutex_unlock(pmutex);
 20   }
 21 private:
 22    pthread_mutex_t* pmutex;
 23 };
 24 
 25 class LockGuard
 26 {
 27 public:
 28    LockGuard(pthread_mutex_t* mutex):_mutex(mutex)
 29    {
 30      //在创建的时候,就自动上锁                                                                                                                                     
 31      _mutex.Lock();
 32    }
 33    ~LockGuard()
 34    {
 35      //销毁的时候,自动解锁
 36      _mutex.Unlock();
 37    }
 38 
 39 private:
 40   Mutex _mutex;
 41 };

这样以后,我们编写代码,将会变得更为优雅
像我们之前实现的抢票代码,只需要在临界区前创建一个对象
然后用一个花括号将临界区括起来,表示其为临界区
则自动加锁,解锁,解决并发问题
多线程(线程互斥),Linux,linux

线程安全和函数重入

首先要意识到
两者谈论的不是一个维度的东西,只能说两者有重叠的部分,但并非是简单的包含与非包含的关系
线程安全是我们必须要保证的!
但大部分函数其实都是不可重入的,因此函数重入并没有好坏之分!仅仅是函数的特征

不可重入函数只是有可能引发线程安全问题,我线程调用的时候不访问全局变量/静态变量,注重各种细节,那就不会引发线程安全问题

常见线程不安全的情况:
1.不保护共享变量的函数
比如我们上述最开始实现的抢票函数
2.函数状态随着被调用,状态发生变化的函数
比如static修饰的静态的函数,每次调用可能都会引发相应状态的改变
3.返回指向静态变量指针的函数
4.调用线程不安全函数的函数

常见线程安全的情况:
1.每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
2.类或者接口对于线程来说都是原子操作(比如说加锁)
3.多个线程之间的切换不会导致该接口的执行结果存在二义性文章来源地址https://www.toymoban.com/news/detail-726983.html

到了这里,关于多线程(线程互斥)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux--线程--互斥锁

    1.互斥量 a)互斥量(mutex)从本质上来说是一把锁,一般在主线程中定义一个互斥量,就是定义一把锁。然后根据我们的需求来对线程操作这把锁。 b)如果给所有的线程都加上锁了,线程们会去争取内存空间,谁先争取到谁先运行,直到该线程解锁后,期间其他线程只能等

    2024年02月06日
    浏览(41)
  • 【linux】线程互斥

    喜欢的点赞,收藏,关注一下把! 到目前为止我们学了线程概念,线程控制接下来我们进行下一个话题,线程互斥。 有没有考虑过这样的一个问题,既然 线程一旦被创建,几乎所有资源都是被所有线程共享的。 那多个线程访问同一份共享资源有没有什么问题? 下面我们模

    2024年02月03日
    浏览(38)
  • linux:线程互斥

    个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C++》《Linux》 本文是对于线程互斥的知识总结 我们先看下面代码。 该代码创建三个线程-1,线程-2,线程-3,去抢夺ticket资源,当ticket从10000依次减到0时,三个线程退出。那该代码运行结果是什么呢?ticket最后会是

    2024年03月21日
    浏览(38)
  • Linux->线程互斥

    前言: 1 线程互斥 1.1 多线程并发问题 1.2 线程锁 1.3 锁的接口 2 线程安全与可重入函数  3 死锁         本篇文章主要讲解了线程互斥的实现方式,还有原理,并附上代码讲解。并且讲解了锁的概念,问题等。         还记得我上一篇文章的结尾有提过的问题吗?如果多个

    2024年02月10日
    浏览(38)
  • Linux多线程互斥锁

    迷途小书童的 Note 读完需要 5 分钟 速读仅需 2 分钟 1 引言 在 Linux 编程中,多线程是一种常见的并发编程模型。为了保证多线程之间的数据同步和互斥访问,pthread_mutex(互斥锁)是一个重要的工具。本文将深入探讨 pthread_mutex 的底层实现原理、函数原型,并提供详细的使用方

    2024年02月10日
    浏览(52)
  • Linux之线程互斥

    目录 一、问题引入 二、线程互斥 1、相关概念 2、加锁保护 1、静态分配 2、动态分配 3、锁的原理 4、死锁 三、可重入与线程安全 1、概念 2、常见的线程不安全的情况 3、常见的线程安全的情况 4、常见不可重入的情况 5、常见可重入的情况 6、可重入与线程安全联系 7、可重

    2024年03月16日
    浏览(87)
  • 【关于Linux中----线程互斥】

    先来用代码模拟一个抢票的场景,四个线程不停地抢票,一共有1000张票,抢完为止,代码如下: 执行结果如下: 可以看到,最后出现了票数为负数的情况,很显然这是错误的,是不应该出现的。 为什么会出现这种情况? 首先要明确,上述的几个线程是不能同时执行抢票的

    2023年04月19日
    浏览(71)
  • 【Linux】多线程互斥与同步

    互斥 指的是一种机制,用于确保在同一时刻只有一个进程或线程能够访问共享资源或执行临界区代码。 互斥的目的是 防止多个并发执行的进程或线程访问共享资源时产生竞争条件,从而保证数据的一致性和正确性 ,下面我们来使用多线程来模拟实现一个抢票的场景,看看所

    2024年02月09日
    浏览(38)
  • Linux——多线程,互斥与同步

    目录 一.linux互斥 1.进程线程间的互斥相关背景概念 2.互斥量mutex 3.加锁互斥锁mutex 4.锁的底层原理  二.可重入VS线程安全 1.概念 2.常见的线程不安全的情况 3.常见的线程安全的情况  4.常见不可重入的情况  5..常见可重入的情况 6.可重入与线程安全联系  三.死锁 1.死锁四个必

    2024年02月05日
    浏览(33)
  • Linux——线程的同步与互斥

    目录 模拟抢火车票的过程 代码示例 thread.cc Thread.hpp 运行结果 分析原因 tickets减到-2的本质  解决抢票出错的方案 临界资源的概念 原子性的概念 加锁 定义 初始化 销毁 代码形式如下 代码示例1: 代码示例2: 总结 如何看待锁 申请失败将会阻塞  pthread_mutex_tyrlock 互斥锁实现

    2024年02月06日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包