本文已参与「新人创作礼」活动,一起开启掘金创作之路。
互斥
当多线程相互竞争操作共享变量时,会发生竞争条件(race condition)
因此我们将此段代码称为临界区(critical section),它是访问共享资源的代码片段,一定不能给多线程同时执行。 这就是互斥。
而解决这种问题的办法就要完成进程间同步
- 锁(自旋锁、无等待锁)
- 信号量
自旋锁的缺陷
信号量
通常信号量表示资源的数量,对应的变量是一个整型(sem)变量。
另外,还有两个原子操作的系统调用函数来控制信号量的,分别是:
- P 操作:将 sem 减 1,相减后,如果 sem < 0,则进程/线程进入阻塞等待,否则继续,表明 P 操作可能会阻塞;
- V 操作:将 sem 加 1,相加后,如果 sem <= 0,唤醒一个等待中的进程/线程,表明 V 操作不会阻塞;
进程互斥的实现方法:
- 软件方式:
-
- Peterson算法
-
硬件方式:
-
- 中断屏蔽
- TestAndSet指令
- swap指令
死锁
死锁的形成条件
- 互斥:线程对于需要的资源进行互斥的访问(例如一个线程抢到锁)
- 持有并等待:线程持有了资源(例如已将持有的锁),同时又在等待其他资源(例 如,需要获得的锁)
- 非抢占:线程获得的资源(例如锁),不能被抢占
- 循环等待:线程之间存在一个环路,环路上每个线程都额外持有一个资源,而这个资源又是下一个线程要申请的
死锁的预防
- 循环等待破解-->顺序获取锁: 获取锁时提供一个线性的锁, 每次都先申请 L1 然后申请 L2...以此类推
//线程 B 函数,同线程 A 一样,先获取互斥锁 A,然后获取互斥锁 B
void *threadB_proc(void *data)
{
printf("thread B waiting get ResourceA \n");
pthread_mutex_lock(&mutex_A);
printf("thread B got ResourceA \n");
sleep(1);
printf("thread B waiting get ResourceB \n");
pthread_mutex_lock(&mutex_B);
printf("thread B got ResourceB \n");
pthread_mutex_unlock(&mutex_B);
pthread_mutex_unlock(&mutex_A);
return (void *)0;
}
-
持有并等待破解--> 原子地抢锁 : 保证了在抢锁的过程中,不发生不合时宜的线程切换
-
非抢占破解-->
-
互斥破解-->通过cas构造不需要锁的数据结构
-
通过调度算法避免死锁:
如果多个线程会互相抢占锁,我们就将其错开,如T1、T2 和 T3 运行在同一个处理器上,这种保守的方案会明显增加完成任务的总时间。但为了避免死锁,我们没有这样做, 而是付出了性能的代价。