概述
上一篇介绍了共享内存,已经属于比较高级的层次。本篇介绍一下多线程/多进程最基础的问题,同步。
为了允许在线程或进程间共享数据,同步常常是必需的,也就是我们常说要用锁(当然锁通常也是性能瓶颈,现在无锁架构正在越发流行)。互斥锁和条件变量则是同步的基本组成部分。互斥锁和条件变量在同一进程下的所有线程内是共享的,所有它天然可以用于线程同步。如果将互斥锁和条件变量存放在多个进程的共享区内,则同样可以用于进程间同步。(Posix标准)
互斥锁
互斥锁用于保护临界区(critical section),保证在任何时刻只有一个线程/进程在执行其中的代码。保护一个临界区的代码通常轮廓如下:
在实际代码中,我们通常使用如下的数据机构和函数。
03 | static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZE; |
06 | int pthread_mutex_lock(pthread_mutex_t *ptr); |
08 | int pthread_mutex_trylock(pthread_mutex_t *ptr); |
10 | int pthread_mutex_unlock(pthread_mutex_t *ptr); |
条件变量
条件变量(condition)不同于互斥锁,它主要用于等待。因为我们在线程/进程同步时,通常都需要等待其他线程/进程完成任务再继续自己的任务。这时,仅仅使用mutex就无法完美的解决这个问题。假设我们只使用mutex,则需要锁上一个mutex然后查询,如果没有前序任务则解锁,然后隔断时间再继续上述过程。而借助条件变量,我们则可以大大简化这个过程。在锁上mutex后,如果没有前序任务则调用wait函数,系统会自动释放mutex并且等待前置信号的到达,且信号到达后还是拥有mutex。
下面是condition的常用函数:
3 | static pthread_cond_t lock = PTHREAD_COND_INITIALIZER; |
6 | int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); |
8 | int pthread_cond_signal(pthread_cond_t *cptr); |
每个条件变量总有一个互斥锁与之关联。(个人猜测原因:因为我们等待条件变量时候,需要释放mutex,交给其他线程)
样例
这里用一个简单的生产者-消费者模型来演示如何使用mutex和condition。
03 | pthread_mutex_t p_mutex = PTHREAD_MUTEX_INITIALIZER; |
04 | pthread_mutex_t c_mutex = PTHREAD_MUTEX_INITIALIZER; |
05 | pthread_cond_t c_cond = PTHREAD_COND_INITIALIZER; |
11 | void *produce( void *arg) { |
15 | pthread_mutex_lock(&p_mutex); |
17 | pthread_mutex_unlock(&p_mutex); |
21 | pthread_mutex_unlock(&p_mutex); |
24 | pthread_mutex_lock(&c_mutex); |
27 | pthread_cond_signal(&c_cond); |
30 | pthread_mutex_unlock(&c_mutex); |
34 | void *consume( void *arg) { |
36 | for ( int i = 0; i < LEN; ++i) { |
37 | pthread_mutex_lock(&c_mutex); |
40 | pthread_cond_wait(&c_cond, &c_mutex); |
43 | pthread_mutex_unlock(&c_mutex); |