# 第十章 条件变量

## 10.1 工作队列

``````typedef struct {
int *array;
int length;
int next_in;
int next_out;
} Queue;``````

`array`是包含队列元素的数组。在这个例子中，元素都是整数，但是通常它们都是一些结构体，包含用户事件、工作项目以及其它。

`length`是数组的长度，`next_in`是数组的下标，用于索引下个元素应该添加到哪里；与之相似， `next_out`是应该被移除的下个元素的下标。

`make_queue`为这个结构体分配空间，并且初始化所有字段：

``````Queue *make_queue(int length)
{
Queue *queue = (Queue *) malloc(sizeof(Queue));
queue->length = length;
queue->array = (int *) malloc(length * sizeof(int));
queue->next_in = 0;
queue->next_out = 0;
return queue;
}``````

`next_out`的初始值需要一些解释。由于队列一开始为空，没有可移除的下一个元素，所以`next_out`是无效的。`next_out==next_in`是个特殊情况，它表示队列为空，所以我们可以编写：

``````int queue_empty(Queue *queue)
{
return (queue->next_in == queue->next_out);
}``````

``````void queue_push(Queue *queue, int item) {
if (queue_full(queue)) {
perror_exit("queue is full");
}

queue->array[queue->next_in] = item;
queue->next_in = queue_incr(queue, queue->next_in);
}``````

``````int queue_incr(Queue *queue, int i)
{
return (i+1) % queue->length;
}``````

``````int queue_full(Queue *queue)
{
return (queue_incr(queue, queue->next_in) == queue->next_out);
}``````

``````int queue_pop(Queue *queue) {
if (queue_empty(queue)) {
perror_exit("queue is empty");
}

int item = queue->array[queue->next_out];
queue->next_out = queue_incr(queue, queue->next_out);
return item;
}``````

## 10.2 生产者和消费者

``````void *producer_entry(void *arg)
{
int i;
Shared *shared = (Shared *) arg;

for (i=0; i<QUEUE_LENGTH-1; i++) {
queue_push(shared->queue, i);
}
}``````

``````void *consumer_entry(void *arg)
{
int i;
int item;
Shared *shared = (Shared *) arg;

for (i=0; i<QUEUE_LENGTH-1; i++) {
item = queue_pop(shared->queue);
printf("consuming item %d\n", item);
}
}``````

``````int i;

Shared *shared = make_shared();

for (i=0; i<NUM_CHILDREN; i++) {
}``````

``````typedef struct {
Queue *queue;
} Shared;

Shared *make_shared()
{
Shared *shared = check_malloc(sizeof(Shared));
shared->queue = make_queue(QUEUE_LENGTH);
return shared;
}``````

• 队列的访问不是线程安全的。不同的线程能同时访问`array``next_in``next_out`，并且会使队列处于损坏的、“不一致”的状态。
• 如果消费者首先被调度，它会发现队列为空，打印错误信息并退出。我们应该阻塞住消费者，直到队列非空。与之相似，我们应该在队列满了的情况下阻塞住生产者。

## 10.3 互斥体

``````typedef struct {
int *array;
int length;
int next_in;
int next_out;
Mutex *mutex;          //-- this line is new
} Queue;``````

``````Queue *make_queue(int length)
{
Queue *queue = (Queue *) malloc(sizeof(Queue));
queue->length = length;
queue->array = (int *) malloc(length * sizeof(int));
queue->next_in = 0;
queue->next_out = 0;
queue->mutex = make_mutex();   //-- new
return queue;
}``````

``````void queue_push(Queue *queue, int item) {
mutex_lock(queue->mutex);   //-- new
if (queue_full(queue)) {
mutex_unlock(queue->mutex);   //-- new
perror_exit("queue is full");
}

queue->array[queue->next_in] = item;
queue->next_in = queue_incr(queue, queue->next_in);
mutex_unlock(queue->mutex);   //-- new
}``````

`queue_pop`的同步代码与之相似：

``````int queue_pop(Queue *queue) {
mutex_lock(queue->mutex);
if (queue_empty(queue)) {
mutex_unlock(queue->mutex);
perror_exit("queue is empty");
}

int item = queue->array[queue->next_out];
queue->next_out = queue_incr(queue, queue->next_out);
mutex_unlock(queue->mutex);
return item;
}``````

## 10.4 条件变量

``````typedef struct {
int *array;
int length;
int next_in;
int next_out;
Mutex *mutex;
Cond *nonempty;   //-- new
Cond *nonfull;    //-- new
} Queue;``````

``````Queue *make_queue(int length)
{
Queue *queue = (Queue *) malloc(sizeof(Queue));
queue->length = length;
queue->array = (int *) malloc(length * sizeof(int));
queue->next_in = 0;
queue->next_out = 0;
queue->mutex = make_mutex();
queue->nonempty = make_cond();   //-- new
queue->nonfull = make_cond();    //-- new
return queue;
}``````

``````int queue_pop(Queue *queue) {
mutex_lock(queue->mutex);
while (queue_empty(queue)) {
cond_wait(queue->nonempty, queue->mutex);  //-- new
}

int item = queue->array[queue->next_out];
queue->next_out = queue_incr(queue, queue->next_out);
mutex_unlock(queue->mutex);
cond_signal(queue->nonfull);   //-- new
return item;
}``````

`cond_wait`有点复杂，所以让我们慢慢来。第一个参数是条件变量。这里我们需要等待的条件是“队列非空”。第二个变量是保护队列的互斥体。在你调用`cond_wait`之前，你需要先锁住互斥体，否则它不会生效。

``````void queue_push(Queue *queue, int item) {
mutex_lock(queue->mutex);
while (queue_full(queue)) {
cond_wait(queue->nonfull, queue->mutex);    //-- new
}

queue->array[queue->next_in] = item;
queue->next_in = queue_incr(queue, queue->next_in);
mutex_unlock(queue->mutex);
cond_signal(queue->nonempty);  //-- new
}``````

• 为什么`cond_wait``while`循环中，而不是`if`语句中？也就是说，为什么在从`cond_wait`返回之后要再次检查条件？

``````需要再次检查条件的首要原因就是信号拦截的可能性。假设线程A在等待`nonempty`，线程B向队列添加元素，之后向`nonempty`发送信号。线程A被唤醒并且尝试锁住互斥体，但是在轮到它之前，邪恶的线程C插进来了，锁住了互斥体，从队列中弹出物品并且解锁了互斥体。现在队列再次为空，但是线程A没有被阻塞。线程A会锁住互斥体并且从`cond_wait`返回。如果线程A不再次检查条件，它会尝试从空队列中弹出元素，可能会产生错误。

> 译者注：有些条件变量的实现可以每次只唤醒一个线程，比如Java对象的`notify`方法。这种情况就可以使用`if`。
``````
• 当人们了解条件变量时，另一个问题是“条件变量怎么知道它关联了哪个条件？”
``````这一问题可以理解，因为在`Cond`结构和有关条件之间没有明显的关联。在它的使用方式中，关联是隐性的。

下面是一种理解它的办法：当你调用`cond_wait`时，`Cond`所关联的条件为假；当你调用`cond_signal`时它为真。当然，可能有一些条件第一种情况下为真，第二种情况下为假。正确的情况只在程序员的脑子中，所以它应该在文档中有详细的解释。
``````

## 10.5 条件变量的实现

``typedef pthread_cond_t Cond;``

`make_cond`分配空间，初始化条件变量，之后返回指针：

``````Cond *make_cond()
{
Cond *cond = check_malloc(sizeof(Cond));
if (n != 0) perror_exit("make_cond failed");

return cond;
}``````

```全选<button href="javascript:void(0);" _xhe_href="javascript:void(0);" class="copyCode btn btn-xs" data-clipboard-text="" void="" cond_wait(cond="" *cond,="" mutex="" *mutex)"="" data-toggle="tooltip" data-placement="bottom" title="" style="color: rgb(255, 255, 255); font-style: inherit; font-variant: inherit; font-stretch: inherit; font-size: 12px; line-height: 1.5; font-family: inherit; margin: 0px 0px 0px 5px; overflow: visible; cursor: pointer; vertical-align: middle; border: 1px solid transparent; white-space: nowrap; padding-right: 5px; padding-left: 5px; border-radius: 3px; -webkit-user-select: none; box-shadow: rgba(0, 0, 0, 0.0980392) 0px 1px 2px; background-image: none; background-color: rgba(0, 0, 0, 0.74902);">复制放进笔记

```void cond_wait(Cond *cond, Mutex *mutex)
{
if (n != 0) perror_exit("cond_wait failed");
}

void cond_signal(Cond *cond)
{
if (n != 0) perror_exit("cond_signal failed");
}``````

## 多线程编程中条件变量和的spurious wakeup 虚假唤醒

1. 概述 条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制.典型的场景包括生产者-消费者模型,线程池实现等. 对条件变量的使用包括两个动作: 1) 线程等待某个条件, 条件为真则继续执行,条件为假则将自己挂起(避免busy wait,节省CPU资源): 2) 线程执行某些处理之后,条件成立:则通知等待该条件的线程继续执行. 3) 为了防止race-condition,条件变量总是和互斥锁变量mutex结合在一起使用. 一般的编程模式: C++代码

## Java并发程序设计(16)并发锁之条件变量

1.1.1. 条件变量应用之等待通知 条件变量Condition提供了一种基于ReentrantLock的事件等待和通知的机制,并且可以监控任意指定的条件,在条件不满足时等待条件满足,其它线程在条件满足时可以通知等待条件的线程,从而唤醒等待中的线程. 下面的代码实现了两件工作分别由两个线程轮流不断执行的效果. package com.test.concurrence; import java.util.concurrent.locks.Condition; import java.util.co

## C++11并行编程-条件变量(condition_variable)详细说明

<condition_variable >头文件主要包含有类和函数相关的条件变量. 包括相关类 std::condition_variable和 std::condition_variable_any,还有枚举类型std::cv_status.另外还包含函数 std::notify_all_at_thread_exit(),以下分别介绍一下以上几种类型. std::condition_variable 类介绍 std::condition_variable是条件变量,很多其它有关条件变量的定义