操作系统笔记(12)-条件变量
操作系统笔记(12)-条件变量
简介
在很多情况下,线程需要检查某一条件满足之后,才会继续运行,比如说主线程可以等待子线程执行完后再继续执行
条件变量可以令线程在条件
未满足
时进入睡眠
,也支持在任务完成后唤醒
另外的线程pthread_cond_wait
调用之前必须持有锁,因为它会释放锁然后使线程进入cond
等待队列,当使用pthread_cond_signal
或pthread_cond_broadcast
时,会将线程从cond
队列移动到mutex
队列,再次获取锁后执行相关任务
父线程等待子线程:使用条件变量
利用
条件变量
实现父线程等待子线程#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> int done = 0; // 另一种初始化的方式 pthread_cond_t c = PTHREAD_COND_INITIALIZER; pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; void* child(void *arg) { pthread_mutex_lock(&m); done = 1; sleep(1); printf("child\n"); pthread_cond_signal(&c); pthread_mutex_unlock(&m); return NULL; } void* thr_join(void *arg) { pthread_mutex_lock(&m); while (done == 0) pthread_cond_wait(&c, &m); printf("parent\n"); pthread_mutex_unlock(&m); return NULL; } int main(int argc, char* argv[]) { printf("parent : begin\n"); pthread_t p; pthread_create(&p, NULL, child, NULL); thr_join(NULL); printf("parent : end\n"); pthread_mutex_destroy(&m); pthread_cond_destroy(&c); return 0; } /* output: parent : begin child parent parent : end */
关注点:
signal()
应该在unlock()
之前还是之后,不同的调用顺序可能有不同的结果。查阅相关资料发现,如果signal()
之后unlock()
,可以保证低优先级线程不会抢占高优先级线程;如果signal()
之前unlock()
,可以降低内核开销,提高效率结论:如果在意调度行为预测,最好先
signal()
再unlock()
,如果在应用层不在意线程优先级和执行顺序,则可以先unlock()
生产者/消费者(有界缓冲区)问题
假设有一个或多个生产者线程和一个或多个消费者线程
生产者把生成的数据放入缓冲区,消费者从缓冲区中取走数据项,以某种方式消费
因为有界缓冲区是共享资源,所以我们必须通过
同步机制
来访问它,以免产生竞态条件
代码实现 :
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFSIZE 16
pthread_cond_t fill, empty;
pthread_mutex_t m;
int buffer[BUFSIZE];
int fill_ptr;
int use_ptr;
int counter;
// 向缓冲区增加一项
void put(int value) {
buffer[fill_ptr] = value;
fill_ptr = (fill_ptr + 1) % BUFSIZE;
counter++;
}
// 向缓冲区取出一项
int get() {
int value = buffer[use_ptr];
use_ptr = (use_ptr + 1) % BUFSIZE;
counter--;
return value;
}
// 生产者线程
void* producer(void *arg) {
int loops = *(int*)arg;
for (int i = 0; i < loops; i++) {
pthread_mutex_lock(&m);
while (counter == BUFSIZE)
pthread_cond_wait(&empty, &m);
put(i);
printf("put : %d\n", i);
pthread_cond_signal(&fill);
pthread_mutex_unlock(&m);
}
}
// 消费者线程
void* consumer(void *arg) {
int loops = *(int*)arg;
for (int i = 0; i < loops; i++) {
pthread_mutex_lock(&m);
while (counter == 0)
pthread_cond_wait(&fill, &m);
int value = get();
pthread_cond_signal(&empty);
printf("get : %d\n", value);
pthread_mutex_unlock(&m);
}
}
int main(int argc, char* argv[]) {
if (argc != 2) {
puts("Please input loops");
return 0;
}
pthread_cond_init(&fill, NULL);
pthread_cond_init(&empty, NULL);
pthread_mutex_init(&m, NULL);
int loops = atoi(argv[1]);
pthread_t pp, pc;
pthread_create(&pp, NULL, producer, (void*)&loops);
pthread_create(&pc, NULL, consumer, (void*)&loops);
pthread_join(pp, NULL);
pthread_join(pc, NULL);
pthread_cond_destroy(&fill);
pthread_cond_destroy(&empty);
pthread_mutex_destroy(&m);
return 0;
}
使用两个条件变量是为了防止如一个生产者多个消费者时,
消费者唤醒消费者
会导致所有线程睡眠的问题使用
while
循环检查而不是if
的原因是当一个线程准备取出缓冲区的一个值时,被另一个线程强先取出,这时缓冲区为空,再取就出问题了,因此采用while
循环检查可以有效避免虚假唤醒
问题
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.