锁与条件变量
Contents
多线程可以很好的提高程序的并发性。但是当多个线程访问公共资源时,必须对临界资源进行保护,否则就会造成访问错乱,没有线程可以正确得到正确的结果。为了解决这个问题,必须要对临界资源进行同步。Linux系统编程中,线程同步处理的方法有:互斥锁、信号量和条件变量。
互斥锁
基本概念
互斥锁的逻辑十分简单,在访问临界资源进行访问时,加上互斥锁,那么该时刻只有一个线程可以获得临界资源。如果其他线程要访问临界资源,那么该线程先会尝试进行上锁,如果这把锁已经被其他线程占用(锁上),那么该线程就会阻塞等待,直到锁解开,该线程才有机会可以上锁成功,进而访问临界资源。
C
语言中关于锁的函数如下:
|
|
锁的类型为pthread_mutex_t
,互斥锁使用前需要进行初始化。互斥锁初始化的方式有两种:
- 静态初始化:定义互斥锁时使用
PTHREAD_MUTEX_INITIALIZER
宏来进行初始化。 - 动态初始化:使用函数
pthread_mutex_init
进行初始化。
在访问临界资源时候,调用pthread_mutex_lock
,进行加锁保护,使得临界资源在解锁之前只有当前加锁线程可以访问。访问结束后,调用pthread_mutex_unlock
,解开互斥锁。其他线程可以获得互斥锁,从而访问临界变量。通过互斥锁,可以使不同线程同步访问临界资源,临界资源不会出现数据错乱。
死锁
当两个或两个以上的线程在执行过程中,因为争夺资源而造成的一种相互等待的状态,由于存在一种环路的锁依赖关系而永远地等待下去,如果没有外部干涉,他们将永远等待下去,此时的这个状态称之为死锁。死锁是锁使用不恰当而产生的现象。
有两种情况会导致死锁。
- 同一个线程先后两次对同一把锁加锁。由于锁已经被占用,所以线程第二次上锁时该线程会阻塞等待解锁,但是锁是被自己占用,但是该线程是被阻塞状态,所以锁无法得到释放,因此该线程会一直阻塞,导致了死锁。
- 有两个线程
A
和B
,同时又两把锁mutex1
和mutex2
,当A
线程获得mutex1
,同时想要获取mutex2
,但与此同时,线程B
获得mutex2
,但同时想要获取mutex1
,这时两个线程都会阻塞,无法执行。
互斥锁的简单程序实例
|
|
如果取消掉注释,程序执行结束会输出result
正确的值2000000
。否则由于对临界变量的访问没有同步,会得到错误的结果:
|
|
条件变量
基本概念
多个协作时,如果有一个线程需要等待某些条件成立才能继续执行,如果条件不成立就阻塞等待。如果没有条件变量的话,线程就需要不断轮询条件是否成立,浪费cpu
资源。条件变量的好处就是可以减少cpu
的轮询,提高效率。
c
中关于条件变量的相关函数:
|
|
条件变量的类型为:pthread_cond_t
,和互斥锁一样,他也有两种初始化方式:使用宏PTHREAD_COND_INITIALIZER
,或者调用pthread_cond_init
,进行初始化。
条件变量最经典的例子就是“生产者-消费者”。生产者线程可以向队列生产数据,而消费者线程从队列中移除数据。当队列中没有数据时,消费者线程就需要等待生产者线程先生产数据。
代码实例
需要注意的地方:
pthread_cond_wait
函数调用后会解开互斥锁,线程会阻塞,直到其他线程通知执行条件满足,即调用pthread_cond_signal
通知阻塞线程,此时阻塞线程会加锁,然后继续执行。- 如果有多个消费者线程,那么在判断条件是否成立时一定要用
while
,否则会产生虚假唤醒现象。具体原理是:如果在线程唤醒之前,有其他线程把条件更改为不满足,那么该阻塞线程在条件不满足的情况下继续往下执行,先然这不是程序预期的执行顺序。此外,还在网上看到相关资料,多线程处理系统中,由于某些底层原因,多线程竞争时,线程没有发出signal
信号,阻塞等待线程也会有一定几率返回。所以在判断条件时,使用while
是十分有必要的。
|
|
Author louis_tian
LastMod 2020-05-12