条件变量的基本介绍与有界缓冲区问题
条件变量
多数情况下,线程需要检查某一条件是否满足之后,才会继续运行。
线程可以使用条件变量来等待一个条件变成真。条件变量是一个显式队列,当某些条件不满足时线程可以把自己加入队列,等待该条件。另外某个线程改变了上述条件时,就可以唤醒一个或多个等待线程,让它们继续执行。
//该函数用于使线程睡眠
pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m)
{//互斥量参数的意义://该函数假定在被调用时该互斥量是已上锁的状态,wait函数负责释放锁,并让//调用线程休眠。当线程被唤醒时,该函数必须重新获取锁,再返回给调用者//这样做的目的是为了避免在线程休眠时产生一些竟态条件
}//该函数用于唤醒线程
pthread_cond_signal(pthread_cond_t *c)
{} //状态变量,标志着子线程是否完成
int done = 0; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER; void thr_exit() {Pthread_mutex_lock(&m); done = 1; Pthread_cond_signal(&c); Pthread_mutex_unlock(&m); } void *child(void *arg) { printf("child\n"); //子线程退出并唤醒父线程thr_exit();return NULL;
} void thr_join() { //获取锁Pthread_mutex_lock(&m); //检查子线程是否完成,未完成则使父线程休眠,这里的done变量是必须的//这里使用while循环比if好while (done == 0) Pthread_cond_wait(&c, &m);//此时子线程得以运行//从wait返回时持有锁,此时释放锁Pthread_mutex_unlock(&m);
} int main(int argc, char *argv[]) { printf("parent: begin\n"); pthread_t p;//创建子线程Pthread_create(&p, NULL, child, NULL); //使子线程运行thr_join(); printf("parent: end\n"); return 0;
}
如果没有done变量,假设子线程先运行,那么父线程就永远得不到运行。done变量记录了线程的状态,是必须的。
假设在join函数中没有互斥量,那么会产生一个竟态条件。父线程在子线程前运行,在即将调用wait函数时,发生了中断使子线程运行。子线程修改done为1,并发送信号使徒唤醒父线程,但是此时父线程在休眠之前被中断了,它并没有处于休眠状态,所以父线程永远不会被唤醒。
总结以下就是发送信号时总是持有锁。
生产者/消费者(有界缓冲区)问题
假设有一个或多个生产者线程和一个或多个消费者线程。生产者把生成的数据项放入缓冲区;消费者从缓冲区取走数据项,以某种方式消费。因为有界缓冲区是共享资源,所以我们必须通过同步机制来访问它,以免产生竞态条件。
//使用一个整形变量来代表缓冲区
int buffer; //该变量用于辨识缓冲区中是否有数据,0为空,1为满
int count = 0; // initially, empty void put(int value) { assert(count == 0); count = 1; buffer = value;
} int get() { assert(count == 1); count = 0; return buffer;
}
发信号给线程只是唤醒它们,暗示状态发生了变化,但并不会保证在它运行之前状态一直是期望的情况。信号的这种释义常称为 Mesa 语义(Mesa semantic),为了纪念以这种方式建立条件变量的首次研究。另一种释义是 Hoare 语义(Hoare semantic),虽然实现难度大,但是会保证被唤醒线程立刻执行。实际上,几乎所有系统都采用了Mesa语义。
关于最终的有界缓冲区问题解决方案,是使用while、两个条件变量,并让生产者在缓冲区满时休眠,让消费者在缓冲区空时休眠。