【Linux系统】线程互斥与同步

本文详细介绍了并发编程中的关键概念,如互斥锁的使用、加锁解锁原理、条件变量、死锁及其解决策略,以及生产者消费者模型。重点强调了原子性、线程安全和避免死锁的方法。
摘要由CSDN通过智能技术生成

目录

一.几个概念

二.线程互斥

1.定义并初始化锁 

2.加锁 

3.解锁 

4.销毁锁

三.互斥锁的本质 

1.xchg的原子性

2.加锁的过程

3.解锁的过程

四.可重入VS线程安全

五.死锁

1.死锁的概念

 2.具体实例

 3.死锁产生的四个必要条件

4.解决或避免死锁

六.线程同步 

七.生产者消费者模型  

1.概念

2.意义

八.条件变量 

1.定义并初始化条件变量

2.等待条件变量

​编辑

3.唤醒等待条件变量的一个或多个线程

 九.条件变量的细节问题


一.几个概念

  1. 临界资源:任何一个时刻,只允许一个执行流访问的共享资源
  2. 临界区:访问(read/write)临界资源的代码
  3. 互斥:保护临界资源的一种访问方式,即任何时刻,保证只有一个执行流进入临界区,访问临界资源
  4. 原子性:一个操作的性质,该操作不会被调度机制打断,只有未完成和已完成两种状态

C++语言中的--操作会转换为三条汇编语句:

  1. 将内存中的数据mov到CPU的寄存器
  2. 对寄存器中的数据dec(减1)
  3. 将寄存器中的数据写入到内存

一条汇编语句才是原子的,因此a--这句代码不是原子的!!!

 举例:两个线程并发对int a = 10进行++操作。线程1将10从内存拷贝到寄存器,进行+1操作,正要准备写2回到内存时,线程1被切换了,它会将寄存器内的值保存到CPU中。线程2从内存中读取到的还是10,进行若干次++操作,加到了100,并将100写回了内存。此时线程1再次被调度,从PCB中恢复硬件上下文,接着上一次未做完的工作,将寄存器中的11写回到内存。线程2好不容易把a加到100,现在一把回到解放前。出现以上问题的原因是,++操作不是原子的,数据从内存到寄存器,从寄存器到内存的过程没有一次性完成,被线程调度打断了。

内存中的数据是共享的,寄存器中的上下文是线程私有的,数据不一致就是指,上下文数据和内存中的共享数据不一致。

二.线程互斥

数据不一致问题,是因为多线程并发访问临界区,访问临界资源。所以只需保证,任一时刻,只有一个线程访问临界区,即可实现对临界资源的保护,以上的资源保护方式叫做互斥,互斥可以通过对临界区加互斥锁的技术手段来完成。

每个线程访问临界区,都必须先加锁,只有加锁成功,才能访问临界区,否则会阻塞等待。这是大家(线程或者编写代码的用户)都应该遵守的规则,如果有的线程遵守,有的不遵守,那锁便失去了意义。

1.定义并初始化锁 

定义并初始化全局的锁: 

定义并初始化局部的锁: 

2.加锁 

  1. 尽可能给少的代码加锁
  2. 一般给临界区加锁
  3. 访问临界区的线程都必须加锁,大家都应遵守这套规则
  4. 任何时刻,只会有一个线程申请加锁成功
  5. 锁也是临界资源,但申请加锁的操作本身就是原子的,所以申请加锁不会出现数据不一致问题,而导致多个线程申请锁成功。
  6. 一个线程加锁成功后,访问临界区,也会发生线程切换。但其它申请锁的线程会在pthread_mutex_lock上阻塞等待,无法访问临界区。加锁间接实现了线程访问临界区的原子性。
  7. 加锁的一般原则:谁加锁,谁解锁

3.解锁 

4.销毁锁

全局锁无需销毁,局部锁要及时销毁:

三.互斥锁的本质 

1.xchg的原子性

计算机通常会提供xchg汇编指令(硬件支持对应机器码),其作用是将寄存器内容和内存单元的内容进行交换。如果机器不支持exchange指令,那我们用户想要完成同样的工作,只能先将寄存器内容备份到另一个寄存器,再将内存拷贝到寄存器,最后将备份的内容拷贝到内存,分三步完成。但机器支持xchg指令,就只需要一步,这意味这,它是原子的。

2.加锁的过程

可以简单认为互斥锁就是一个值为变量,值为1表示可以被申请,值为0表示被占用

1.将某个寄存器的内容置0

2.使用xchg交换寄存器和mutex的内容->寄存器内容变为mutex的值,mutex变为0

3.判断寄存器内容是否大于0,如果大于0,则成功返回,否则挂起等待

只有第二步涉及到访问内存中的共享资源,而第二步又是原子的,所以整个加锁过程就是原子的。

内存中的mutex是线程共享的,寄存器中的上下文是线程私有的。xchg的本质是将共享资源mutex的数据交换到上下文中,属于线程自己。互斥锁的“1”就像一个令牌,谁拥有了“1”,谁就拥有了锁,即使线程被切换,它也会将上下文保存到PCB中打包带走。

3.解锁的过程

1.将1mov到内存的mutex中

2.唤醒等待mutex的线程(只不过从是从阻塞状态唤醒,可以被CPU调度,不是直接把锁分配给它),并成功返回

  

可以看出,解锁也是原子的

四.可重入VS线程安全

  1. 一个函数被多个执行流进入,称该函数被重入了。如果因为函数被重入而使程序行为不符合预期(崩溃或者数据不一致),称为该函数不可重入;否则,则称该函数可重入
  2. 多线程并发访问共享资源时,程序的行为符合预期,则称该线程是安全的。
  3. 线程安全描述的是线程的特征,可重入描述的是函数的特征。
  4. 多线程调用不可重入函数会导致线程不安全,多线程调用可重入函数不会导致线程不安全

五.死锁

1.死锁的概念

一组进程中的各个进程均占有不会释放的资源,但因为相互申请被其他进程所占用不会释放的资源,而处于的一种永久等待的状态。简而言之,死锁就是线程等待一个永远都得不到的锁

 2.具体实例

实例1:现有某个共享资源,规定线程访问它需要持有锁1和锁2。线程A欲要访问该资源,已经持有锁1,正在申请锁2;线程B也想访问该资源,已经持有锁2,正在申请锁1。线程A和线程B都无法集齐两把锁,也都不放弃手中的锁,导致线程A和线程B都被阻塞挂起,永远不会被调度。

以上是两把锁造成的死锁,实际上一把锁也能造成死锁

实例2:由于用户的粗心,将解锁代码写成加锁,导致线程等待自己手中的锁,但它永远等不到

 3.死锁产生的四个必要条件

  1. 互斥条件:使用锁以达到访问共享资源互斥
  2. 请求与保持条件:线程在申请锁的同时,对已获得的锁保持不放
  3. 不剥夺条件:一个线程手中的锁在使用完之前,不会被其它线程强行释放
  4. 循环等待条件:A找B要,B找A要

4.解决或避免死锁

破坏产生死锁的4个条件之一即可:

  1. 破坏互斥条件。使用锁是因为要保护共享资源,在条件允许的情况下,让每个线程私有一份资源,这样就不必使用锁来保证互斥了
  2. 破坏请求与保持条件。如果一个线程申请锁不成功,就把持有的锁都释放掉
  3. 破坏不剥夺条件。线程A想申请线程线程B手中的,管理线程检测到死锁,强行将线程B的锁释放
  4. 破坏循环等待条件。如果多线程要申请相同的锁,建议按照同样的次序申请

用户编码建议:

  1. 能不用锁就不用锁
  2. 如果要线程需要申请多个锁,尽量按照顺序一次性申请到位,不要分批申请

六.线程同步 

同步:在临界资源使用安全的前提下,让多线程执行具有一定的顺序性,以保证合理使用资源,解决线程饥饿问题。线程同步使用条件变量完成。

互斥能保证资源的安全,同步让资源使用更加高效。

七.生产者消费者模型  

1.概念

生产者消费者模型是多线程并发的一种经典设计模式。分为两种角色:

生产者:产生数据的线程,将产生的数据放置到共享的内存空间

消费者:从共享的内存空间取数据进行处理

生产者和消费者之间的关系如下:

生产者和生产者:互斥。同时向一块内存写入,会出现数据不一致问题(覆盖或混乱)

消费者和消费者:互斥。同时从一块内存读数据,会出现数据不一致问题(混乱)

生产者和消费者:互斥和同步。同时写和读,会导致消费者得到的数据残缺不全,所以需要互斥;生产者生产完成后再通知消费者来取,避免消费者做一些浪费资源的行为,所以需要同步。

注意:生产是指将数据放到交易场所,消费是指从交易场所拿走数据

2.意义

我们调用函数的目的,就是让函数作为消费者,处理传递给它的数据。但是如果函数的处理过程没有完成,调用处的代码就无法向下执行。

  1. 实现生产过程和消费过程执行解耦。共享的内存空间相当于一块缓存,只要缓存没有打满或者缓存不为空,消费和生产过程就不会相互影响,支持生产消费忙闲不均
  2. 提高数据处理的效率。生产者生产数据,前提是要获取数据,消费者取走数据后,还要进行处理。消费者的数据一般从具体场景中得来,例如从网络读取,也就是说,获取数据本身也要花费时间,消费者处理数据也要花时间。多生产和多消费模型在任何一个时刻只允许一个线程进入,高效并不体现在同步和互斥,而是使得获取数据和处理数据更好地并发。

八.条件变量 

1.定义并初始化条件变量

2.等待条件变量

3.唤醒等待条件变量的一个或多个线程

 前者是唤醒所有的线程,或者是唤醒队头的线程

 九.条件变量的细节问题

  1. 在加锁和解锁之间要往往是要访问临界资源,但资源不一定是就绪的,所以需要有if语句这样的判断。如果判断条件不满足,什么也不做,线程会再次执行加锁,判断,解锁这样的无效工作。为了避免资源浪费,在条件不满足时,让线程在条件变量上阻塞等待。
  2. 让线程进行等待的时候,会自动释放锁
  3. 线程被唤醒的时候,是在临界区内唤醒的,当线程被唤醒,线程在pthread_cond_wait返回的时候,要重新申请锁  
  4. 当线程被唤醒的时候,重新申请锁本质是也要参与锁的竞争的
  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值