1. 思维导图
Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量、信号量和读写锁。
下面是思维导图:
2. 各种解决方法的比较
并发有两大需求,一是互斥,二是等待。互斥是因为线程间存在共享数据,等待则是因为线程间存在依赖,需要同步。从上面可以看出,在Linux内核中有互斥锁、条件变量、读写锁、信号量4种机制用于解决线程间的同步和互斥问题,那这四种机制有何区别?
2.1 互斥锁
互斥量有两种状态–解锁和加锁。当一个线程(或进程)需要访问临界区时,它调用互斥锁。如果该互斥量当前是解锁的(即临界区可用),此调用成功,调用线程可以自由进入该临界区。另一方面,如果该互斥量已经加锁,调用线程被阻塞,直到在临界区中的线程完成并调用互斥锁。如果多个线程被阻塞在该互斥量上,将随机选择一个线程并允许它获得锁。
案例:这里有一个打印机p,A和B都要使用,显然只能轮流着使用。这时就需要加一个互斥锁,只有得到了互斥锁lock_s之后才能进行打印
pthread_mutex_lock(lock_s);
print();
pthread_mutex_unlock(lock_s);
2.2 条件变量(事件)
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和锁机制同时使用。条件变量,是为了解决等待同步需求,实现生产者消费者队列的一种机制(得益于事件驱动模式——Actor,他也是基于异步AIO出现的,其实现在很多东西都是来自于Actor思想,比如IO多路复用、MapReduce、消息队列中间件(注意不是os中进程间通信的消息队列,这个消息队列中间件算是建立在应用层上的,而非os级的消息队列))。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使 “条件成立”(给出条件成立信号)。
【原理】:
条件的检测是在互斥锁的保护下进行的。线程在改变条件状态之前必须首先锁住互斥量。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。
【条件变量的操作流程如下】:
1. 初始化:init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER;属性置为NULL;
2. 等待条件成立:pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真 timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait);
3. 激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
4. 清除条件变量:destroy;无线程等待,否则返回EBUSY清除条件变量:destroy;无线程等待,否则返回EBUSY
案例:生产者P每次只能生产1个配件part,消费者A和B必须要等到有100个配件才能进行装配。
如果我们只用互斥锁的话,那消费者就得互斥地方式不断轮询共享的全局变量part,查看其数量是否大于等于100。如果是队列里有值,就去消费;如果为空,要么是继续查( spin 策略),要么 sleep 一下,让系统过一会再唤醒你,你再次查。可以想到,无论哪种策略,都不通用,要么费 cpu,要么线程 sleep过头了,影响该线程的性能。
//poll轮询
#define assembleMutex
P:
part++
A/B: