为什么引入条件变量
考虑如下情形:A线程负责删除链表中的一个元素,B线程负责往链表中插入一个元素。对于一个空链表,只有B线程先执行过了,A线程能够执行。
条件变量适用于这样的情形:两个线程完成的任务之间有明确的先后顺序。
条件变量是一个显示队列,当条件不满足时,线程可以把自己加入队列,等待该条件。如果某个线程改变了该条件,那么它可以唤醒一个或多个等待线程。
条件变量API
//数据结构
pthread_cond_t cond;
pthread_mutex_t mutex;
//初始化
pthread_cond_init(&cond, NULL);
//等待,需要提供锁,因为要将锁释放,详见后续
pthread_cond_wait(&cond, &mutex);
//发射信号
pthread_cond_signal(&cond);
条件变量的使用
条件变量的使用需要配合一个锁和一个done变量。为什么需要?
考虑一个父线程等待子线程完成的实例:
#include <cstdio>
#include <pthread.h>
int done = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void thre_exit() //son exit
{
pthread_mutex_lock(&mutex);
done = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
void thr_join() //father wait son
{
pthread_mutex_lock(&mutex);
while (done == 0)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
}
void* sonThread(void* arg)
{
printf("son:begin\n");
thre_exit();
return NULL;
}
int main()
{
printf("parent:begin\n");
pthread_t son;
pthread_create(&son, NULL, sonThread, NULL);
thr_join();
printf("Parent:end\n");
return 0;
}
为什么需要done
如果子线程在父线程之前执行,那么可能父线程还没有调用wait,子线程已经signal了,父线程会长眠不醒。
为什么需要锁
这里存在一个微妙的竞态条件。如果thr_join执行时,在wait前发生了不合时宜的中断,那么如果子线程执行thr_exit,就会导致父线程长睡不醒。
为什么只用一个done不行
最简单的方案就是父线程与子线程共享一个变量done,进入子线程之前设置done为0, 当done==1时,意味着子线程执行完毕。
该方案的问题出在父线程的自旋上,性能太差。
为什么join中要用while
这个算是一种好的习惯,就本例子而言while与if是等价的。但有时会出现多个等待线程竞争的情况,即使从wait中醒来,也不应该执行。
信号的Mesa释义:发信号给线程只是唤醒它们,暗示状态发生了变化,并不保证它运行之前状态一直是期望的情况。
生产者、消费者问题(有界缓冲区)
假设有一个或多个生产者进程与一个或多个消费者线程。生产者将生成的数据放入缓冲区,消费者将缓冲区的数据取走。
使用条件变量解决有界缓冲区问题:
存在一个生产者线程与两个消费者线程,缓冲区的大小为8,生产者执行一次往缓冲区加入10个数,一个消费者执行一次从缓冲区中读取5个数。
可以验证了full与empty条件变量的发送、等待。
#include <iostream>
#include <algorithm>
#include "pthread.h"
const int MAX = 8;
int fill_ptr = 0, use_ptr = 0;
int cnt = 0;
int buffer[MAX];
void put(int value)
{
buffer[fill_ptr] = value;
fill_ptr = (fill_ptr + 1) % MAX;
cnt++;
}
int get()
{
int ret = buffer[use_ptr];
use_ptr = (use_ptr + 1) % MAX;
cnt--;
return ret;
}
pthread_cond_t empty, full;
pthread_mutex_t lock; //why lock?
void* producer(void* arg)
{
printf("This is producer\n");
for (int i = 0; i < 10; i++)
{
pthread_mutex_lock(&lock);
while (cnt == MAX)
pthread_cond_wait(&empty, &lock);
put(i);
printf("producer:%d\n",i);
pthread_cond_signal(&full);
pthread_mutex_unlock(&lock);
}
}
void* consumer(void* arg)
{
char* name = static_cast<char*>(arg);
printf("This is consumer%s\n", name);
for (int i = 0; i < 5; i++)
{
pthread_mutex_lock(&lock);
while (cnt == 0)
pthread_cond_wait(&full, &lock);
int tmp = get();
pthread_cond_signal(&empty);
pthread_mutex_unlock(&lock);
printf("%s:%d\n", name, tmp);
}
}
int main()
{
pthread_t pro, cons1, cons2;
char A[] = "A", B[] = "B";
pthread_cond_init(&empty, NULL);
pthread_cond_init(&full, NULL);
pthread_create(&pro, NULL, producer, NULL);
pthread_create(&cons1, NULL, consumer, (void*)A);
pthread_create(&cons2, NULL, consumer, (void*)B);
pthread_join(pro, NULL);
pthread_join(cons1, NULL);
pthread_join(cons2, NULL);
}