线程安全以及线程安全的实现(同步与互斥的实现)

线程安全:

多个线程(执行流)间对临界资源进行争抢访问而不会造成数据二义或逻辑混乱
线程安全的实现:
互斥:同一时间,只有一个线程能够访问临界资源来保证操作安全性
同步:通过条件的判断,实现对临界资源访问的时序合理性

Linux线程互斥

进程线程间互斥相关背景概念

  • 临界资源:多线程执行流共享的资源叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用

互斥量mutex

  • 大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互
  • 多个线程并发的操作共享变量,会带来一些问题

互斥的实现:

要解决一些问题,需要做到三点:

  • 代码需要有互斥行为:当一个线程进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能组织其他线程进入临界区。
    本质上就是需要一把锁。linux上提供的这把锁叫互斥量

互斥锁

互斥锁实现互斥的原理:只有0和1的计数器,用于标记当前临界资源的访问状态;对临界资源进行访问的时候要先去加锁访问这个状态计数以及若可以访问的话则修改计数(这个计数的操作本身就是原子操作)
互斥锁变量类型:pthread_mutex_t 它其实是一个结构体,互斥锁里面不仅仅是一个计数器,还要有等待队列,因为要唤醒等待队列中的pcb
锁的初始化:pthread_mutex_init(pthread_mutex_t *mutex, pthread_muexattr_t *attr)
第一个参数:互斥锁变量
第二个参数:互斥锁属性变量 通常置空不设置
加锁
pthread_mutex_lock(pthread_mutex_t *mutex) ——阻塞加锁,若加不了锁,则一直等待
还有几个可以了解的:
pthread_mutex_trylock ——非限制加锁,加不了锁就返回
pthread_mutex_timelock ——限制阻塞时长的阻塞加锁
解锁
pthread_mutex_unlock(pthread_mutex_t *mutex)

释放锁资源:pthread_mutex_destroy (pthread_mutex_t *mutex);

死锁:

什么是死锁? 死锁是怎样产生的
多个线程对资源进行争抢访问,但是因为推进的顺序不当,导致相互等待,造成程序流程无法继续推进,这就是死锁的产生

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

  1. 互斥条件 :锁同一时间只有一个线程能够获取,别人不能加锁
  2. 不可剥夺条件 :锁只有一个线程可以加锁,只有这个线程才能解锁
  3. 请求与环路保持条件 :拿着A锁去请求B锁,若请求不到,则一直保持A的锁,不释放A锁
  4. 环路等待条件 :两个(多个)线程与两个(多个锁)之间的一种状态

预防死锁

  • 破坏死锁的四个必要条件
  • 加锁的顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配
    避免死锁
  • 银行家算法

Linux线程同步

同步的实现

条件变量:条件变量提供了让一个线程等待与唤醒的功能
条件变量实现同步的原理:向外提供一个等待队列,以及等待与唤醒的功能接口。
注意但是条件变量并没有条件判断的功能条件变量不具备判断什么时候该等待或该唤醒的功能,因此条件判断需要用户自身完成
同步的
接口
pthread_cond_t ——条件变量类型(结构体)
pthread_cond_init()——初始化条件变量
pthread_cond_wait()——让一个线程阻塞(将pcb加入条件变量的等待队列,也就是加入阻塞队列)
关于wait()接口:条件在多个线程中,条件本身就是一个临界资源,需要互斥锁来保护
pthread_cond_siginal(pthread_cond_t *cond)——通过条件变量唤醒等待队列上的等待线程

为什么pthread_cond_wait 需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一致等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件变量不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要有互斥锁来保护,没有互斥锁就无法安全的获取和修改数据。
    注意事项
  1. 用户的条件判断需要使用while循环进行判断(被唤醒的多个线程,有可能都等 待在锁上,解锁后,这时候有可能线程在不具备访问条件的情况下,加锁成功进行资源访问)
  2. 不同的角色要使用多个条件变量并且等待在不同的等待队列上

你可能感兴趣的:(Linux)