线程安全: 描述一个线程中的操作是否使安全的,而不会造成数据二义性
在多线程中,会对临界资源进行争抢访问,常常会造成数据二义性或者逻辑异常,线程安全就是为了避免这种情况的发生。
如何实现线程安全
通过同步与互斥实现线程安全
同步:通过条件判断实现对临界资源访问的合理性
同步的实现:条件变量 --- 提供一个pcb等待队列,提供了两个接口(线程等待 / 线程唤醒),条件变量在实现同步的时候,自身并没有提供条件判断的接口,也就是说,条件变量也不知道什么时候让线程休眠,什么时候唤醒线程,获取资源条件是否满足的判断需要程序员自己在程序中实现
接口介绍:
1.定义条件变量 pthread_cond cond
2.初始化条件变量 pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* arrt);
3.在执行流不满足资源获取条件的时候调用, pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex),条件变量需要搭配互斥锁一起使用
4.在执行流满足获取资源的条件之后唤醒等待的执行流 pthread_cond_signal(pthread_cond_t *cond) ---至少唤醒一个pcb
pthread_cond_broadcast(pthread_cond_t * cond) --- 唤醒所有的执行流
5.不使用条件变量则释放资源 pthread_cond_destroy(pthread_cond_t * cond)
互斥:通过同意一时间只能有一个执行流能够访问临界资源实现访问的安全性
互斥如何实现:互斥锁
互斥锁:就是只有0/1的计数器,描述访问临界资源的访问状态,一个执行流在访问期间就要将资源状态标记为不可访问,访问完毕之后,将状态标记为可访问
这些访问同一临界资源的执行流在访问之前都,都要先访问互斥锁,判断访问状态
操作流程
- 定义互斥锁变量 pthread_mutex mutex;
- 初始化互斥锁变量 pthread_mutex init(pthread_mutex, pthread_mutexattr_t *attr);
- 在访问临界资源前加锁 pthread_mutex_lock(pthread_mutex_t *mutex); / pthread_mutex_trylock(pthread_mutex_t *mutex);
- 对临界资源访问完毕,解锁pthread_mutex_unlock((pthread_mutex_t *mutex);
- 如果不使用互斥锁则销毁互斥锁 pthread_mutex_destroy((pthread_mutex_t *mutex);
注意事项:
1.加锁保护的是临界资源的操作,尽量不要保护不相干操作 -- 增加了冲突后的等待时间
2.枷锁之后,在任意可能退出的线程的地方都要进行解锁
3.互斥锁只能保护资源访问的安全性,不能保证访问的合理性
死锁:多个执行流因为锁资源的争抢,但是因为推进的顺序不当,陷入互相等待,导致程序流程无法继续推进
死锁产生的四个条件:产生的四个必要条件
- 互斥条件 ---- 同一时间只有一个执行流加锁
- 不可剥夺条件 ---- 哪个执行流加的锁,必须由哪个执行流进行解锁,其它执行流无法解锁
- 请求与保持条件 ----我加了A锁,然后去加B锁,如果不能加B锁,也不释放A锁
- 环路等待条件 ---- 我加了A锁,然后去加B锁,对方加了B锁,然后去加A锁(加锁顺序不当引起的)
如何预防死锁的产生:如果加不了B锁,就将A锁释放掉,保证所有执行流加锁顺序的一致
如何避免产生死锁:银行家算法等
银行家算法:将系统分为安全和不安全状态,列出三张表分别为都有哪些锁、哪些执行流分配了哪些锁、哪个执行流想要加哪个锁,通过这三个表可以判断出在给执行流分配它想要的锁是否会造成环路等待,使系统进行不安全状态,如果有这个可能,就不会给执行流分配