使用多线程处理数据时的线程安全问题,同步与互斥的实现以及死锁的产生条件

线程安全

首先我们要知道什么是线程安全!
线程安全指的是多个执行流进行任务处理的时候对临界资源的争抢访问, 不会产生数据二义问题。

为了实现线程安全,我们通常需要保证同步与互斥这两个条件。
同步:通过条件判断保证对临界资源访问的合理性
互斥:通过同一时间的唯一访问保证临界资源访问的安全性

同步与互斥的实现
互斥的实现

互斥的实现我们通常是利用互斥锁
互斥锁本身实际上是一个只有0/1(计数为1或者为0)的计数器,通过这样的计数描述当前临界资源的访问状态,所有执行流在访问临界资源的时候都需要通过该计数器判断当前临界资源的访问状态,如果不允许访问,则让执行流挂起等待;如果可以访问,会在访问期间将该临界资源的访问状态修改为不可访问状态,这期间其它的执行流想要访问则不被允许。

关于互斥锁的操作流程及其相关借口介绍

  1. 定义一个互斥锁变量

     pthread_mutex_t mutex;//定义一个互斥锁变量
    
  2. 初始化互斥锁

     pthread_mutex_init(pthread_mutext_t* mutex, pthread_mutexattr_t* attr);//初始化互斥锁变量
     //也可以通过一个宏来初始化互斥锁
     pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
  3. 加锁操作
    通常用于在访问临界资源之前加锁,保护访问临界资源的操作,不能加锁则挂起等待,可以加锁时修改临界资源的访问状态,然后调用返回,访问临界资源。

     pthread_mutex_lock(pthread_mutex_t* mutex);//阻塞加锁,不能加锁则等待
     pthread_mutex_trylock(pthread_mutex_t* mutex);//非阻塞加锁,不能加锁则报错返回
    
  4. 解锁操作
    通常用于在访问临界资源之后,进行解锁操作(只加锁不解锁会导致其它执行流一直无法访问被该锁保护的临界资源)

     pthread_mutex_unlock(pthread_mutex_t* mutex);//解锁,将临界资源的访问状态置为可访问
    
  5. 互斥锁的销毁

pthread_mutex_destroy(pthread_mutex_t* mutex);//销毁互斥锁

我们要注意的是,所有的执行流都是通过这个互斥锁实现互斥的操作的,而且我们说互斥锁本身其实就是一个计数器,这也就是说,互斥锁本身其实也是一个临界资源,大家都回去访问,因此互斥锁本身必须是安全的,它自身的计数操作是一个原子操作

死锁

在我们的实际应用当中,有时候会需要多个互斥锁,而多个执行流就会对锁资源进行争抢访问,但是因为程序推进顺序不当,造成一种互相等待的情况,这个时候程序执行流就无法继续推进,这其实就是死锁
简单来说就是死锁就是程序执行流无法推进,卡在某一位置的一种概念

死锁产生的必要条件
  1. 互斥条件:指的是我加了锁,别人就不能在加锁了
  2. 不可剥夺条件:我加了锁,其他人都不能解开我加的锁
  3. 请求与保持条件:我加了一把A锁,然后去请求加B锁,但这个时候我无法请求到B锁,我也不去释放A锁
  4. 环路等待条件:我加了A锁去请求B锁,另一个人加了B锁来请求A锁

知道了死锁产生的四个必要条件,这个时候我们要预防死锁的产生,其实就是破坏死锁产生的四个必要条件(通常主要是避免3和4这两个条件)

关于死锁的避免:常用的有银行家算法 / 死锁检测算法

这里简单理解以下银行家算法的思路
利用三张表,一张表记录当前有哪些锁,一张表记录当前已经给谁分配了哪些锁,一张表记录当前还需要哪些锁
按照这三张表进行判断,判断给一个指定的执行流分配指定的锁,是否会造成环路等待条件导致死锁,如果有可能就不分配这个锁,反之,如果分配了这个锁不会造成环路等待条件,就分配这个锁。这其实就破坏了死锁产生的环路等待条件。
在后续的锁资源分配当中,可以资源回溯,把当前执行流中已经加的锁释放掉,这其实就破坏了死锁产生的请求与保持条件。

实现线程安全并不是保证互斥就可以,互斥只是保证了对临界资源访问的安全性,但是它并不保证合理。因此还需要同步的实现(同样的同步保证不了安全)

同步的实现

同步的实现我们通常是利用条件变量
条件变量实现同步其实非常简单,提供了两个功能接口,一个接口让执行流挂起等待,一个接口唤醒等待的执行流。对当前是否满足资源获取的条件进行判断,不满足则让执行流等待,等到条件满足的时候再唤醒执行流。

关于条件变量的操作流程及其相关借口介绍

  1. 类似于互斥锁,首先定义并初始化条件变量

     pthread_cond_t cond//定义一个条件变量
     pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* attr);//初始化条件变量
     //当然条件变量也可以使用宏进行初始化
     pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
  2. 若资源获取条件不满足的时候,调用接口使执行流等待

     pthread_cond_wait(pthread_cond_t* condm, pthread_mutex_t* mutex);//条件变量搭配互斥锁一起使用
     pthread_cond_timedwait(pthread_cond_t* condm, pthread_mutex_t* mutex, struct timespec* );//设置阻塞超时的等待接口
    
  3. 若资源获取条件满足的时候,调用接口唤醒等待的执行流

     pthread_cond_signal(pthread_cond_t* cond);//唤醒至少一个等待中的执行流,有可能是多个
     pthread_cond_broadcast(pthread_cond_t* cond);//唤醒所有等待中的执行流
    
  4. 销毁条件变量

     pthread_cond_destroy(pthread_cond_t* cond);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值