多线程总结2-条件变量
0. 条件变量
条件变量是pthread线程库为线程同步提供的另外一种机制。它允许线程在某条件没有到达的情况下投入睡眠,在条件到达之后被唤醒。条件变量通常与互斥锁配合起来使用。
1. 相关函数
1.1. 初始化
条件变量的类型是pthread_cond_t
,它可以通过两种方式进行初始化。
- 静态分配的条件变量可以通过常量
PTHREAD_COND_INITIALIZER
初始化, 如:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
- 动态分配的条件变量必须通过下面两个函数进行初始化和销毁
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
1.2. 发送信号
当条件到达时,我们可以通过下面两个函数唤醒等待队列中的线程。
// 唤醒等待队列中的一个线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒等待队列中的所有线程,并重新参与调度
int pthread_cond_broadcast(pthread_cond_t *cond);
1.3. wait函数
pthread_cond_wait
是一个阻塞函数,当等待的条件不满足时,线程阻塞在此函数。wait函数将线程投入等待队列,同时将传入的mutex解锁,这两个步骤是原子的。当wait函数返回时,mutex再次被加锁。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
2. 实例
下面我们来看一个利用条件变量来实现多线程同步,模拟文件下载过程的例子。在这个例子当中我们希望用多个线程(线程个数由用户输入)同时下载,用一个线程来检查下载进度,当下载完成,结束所有线程。
// download thread
void *do_download(void * arg)
{
int i = 0;
for (i = 0; ; i++) {
pthread_mutex_lock(&qlock); // 加锁检查
if (process_bar > 100) {
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&cond); // 下载完成,发送信号
return ((void*)0);
}
printf("downloading......%d%%\n", process_bar);
process_bar++;
pthread_mutex_unlock(&qlock);
sleep(1); // 假设1s可以下载1%
}
}
// check download status thread
void *check_download_status(void* arg)
{
pthread_mutex_lock(&qlock);
printf("is download complete?\n");
while (process_bar < 100) {
printf("%d%%......\n", process_bar);
pthread_cond_wait(&cond, &qlock); // 等待下载完成信号
printf("is download complete?\n");
}
printf("download success!\n");
pthread_mutex_unlock(&qlock);
return ((void*)0);
}
// main thread
int process_bar = 0;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 条件变量初始化
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; // mutex初始化
int main(int argc, char* argv[])
{
pthread_t *tid;
int err, i, cnt;
if (argc != 2) {
printf("usage:./a.out threadnum(threadnum >= 2)\n");
exit(0);
}
cnt = atoi(argv[1]); // 线程个数由用户输入
if (cnt < 2) {
printf("thread num must be no less than 2\n");
exit(0);
}
tid = (pthread_t*)malloc(sizeof(pthread_t) * cnt);
for (i = 0; i < cnt - 1; i++) { //开启cnt-1个线程同时下载
err = pthread_create(&tid[i], NULL, do_download, NULL);
if (err != 0) {
printf("create thread error\n");
exit(-1);
}
}
// 开启检查线程
err = pthread_create(&tid[cnt - 1], NULL, check_download_status, NULL);
if (err != 0) {
printf("create thread error\n");
exit(-1);
}
for (i = 0; i < cnt; i++) {
pthread_join(tid[i], NULL);
}
free(tid);
exit(0);
}
3. 运行结果
上面这个例程的运行效果是这样
is download complete?
0%......
downloading......0%
downloading......1%
...
downloading......99%
downloading......100%
is download complete?
download success!
如果只有1个下载线程,那么这个程序大概需要运行100s,如果开启100个下载线程,这个程序只需要运行1s多一点。想一下,如果开启1000个线程需要运行多久呢?
例程下载:https://github.com/zkangHUST/OperatingSystem/blob/master/thread/11.c
4. 参考资料
[1.] 《unix环境高级编程》第11章
[2.] Allen’s Blog https://allen.blog.csdn.net/article/details/61926511