上一篇我们介绍了锁的概念,通过硬件和操作系统提供的能力来实现锁。然而,锁并不是并发程序设计所需的唯一原语。 具体来说,在很多情况下,线程需要检查某一条件(condition)满足之后,才会继续运行。例如线程的 Join 函数。
条件变量
线程可以使用条件变量(condition variable)(,来等待一个条件变成真。条)变量是一个显式队列,当某些执行状态(即条件,condition)不满足时,线程可以把自己加入队列,等待(waiting)该条件。另外某个线程,当它改变了上述状态时,就可以唤醒一个或者多个等待线程(通过在该条件上发信号),让它们继续执行。
信号量
信号量是有一个整数值的对象,可以用两个函数来操作它。在 POSIX 标准中,是 sem_wait() 和 sem_post():
#include <semaphore.h> sem_t s; sem_init(&s, 0, 1);
|
首先,sem_wait() 要么立刻返回(调用 sem_wait() 时,信号量的值大于等于 1),要么会让调用线程挂起,直到之后的一个 post 操作。当然,也可能多个调用线程都调用 sem_wait(),因此都在队列中等待被唤醒。
其次,sem_post() 并没有等待某些条件满足。它直接增加信号量的值,如果有等待线程,唤醒其中一个。
最后,当信号量的值为负数时,这个值就是等待线程的个数。
接下来我们介绍一些信号量的应用。
二值信号量(锁)
sem_t m; sem_init(&m, 0, 1); sem_wait(&m);
sem_post(&m);
|
上例是用信号量来实现锁。因为锁只有两个状态,所以这种用法有时也叫作二值信号量(binary semaphore)。
信号量用作条件变量
信号量也可以用在一个线程暂停执行,等待某一条件成立的场景。
sem_t s;
void *child(void *arg) { printf("child\n"); sem_post(&s); return NULL; }
int main(int argc, char *argv[]) { sem_init(&s, 0, 0); printf("parent: begin\n"); pthread_t c; Pthread_create(c, NULL, child, NULL); sem_wait(&s); printf("parent: end\n"); return 0; }
|
实现信号量
利用底层的同步原语(锁和条件变量),来实现一个信号量:
typedef struct _Zem_t { int value; pthread_cond_t cond; pthread_mutex_t lock; } Zem_t;
void Zem_init(Zem_t *s, int value) { s->value = value; Cond_init(&s->cond); Mutex_init(&s->lock); }
void Zem_wait(Zem_t *s) { Mutex_lock(&s->lock); while (s->value <= 0) Cond_wait(&s->cond, &s->lock); s->value--; Mutex_unlock(&s->lock); }
void Zem_post(Zem_t *s) { Mutex_lock(&s->lock); s->value++; Cond_signal(&s->cond); Mutex_unlock(&s->lock); }
|