一、什么是条件变量
条件变量是一种同步互斥机制,通常与互斥锁一起使用以实现线程之间的通信和同步。
二、问题的引入
先来看一个例子:小楠是一名在校学生,每个月都会从父母那里得到一笔生活费。现在她的钱花光了,想要去取钱。但是很显然取钱这样的事情不是想干就能干的,前提是卡里必须得有钱才行!于是小楠拿起手机一查发现:余额为¥0。现在她除了干瞪眼,唯一能干的事情也许只有一件:等。等到她爸妈汇了钱打电话通知她为止。
但更进一步,即便是她爸妈汇了钱也打了电话通知了她,此刻她也不能一定保证能取到钱,因为与此同时她的众多兄弟姐妹(统统共用一个银行账号)很可能已经抢先一步将钱悉数取光了!因此当小楠收到爸妈的电话之后,需要再次确认是否有钱,才能取钱。
三、使用场景
(1)、 生产者-消费者模式:多个线程生产数据,多个线程消费数据。消费者等待条件变量,当生产者生产数据时唤醒消费者。
(2)、 服务器程序:服务器可以使用条件变量来实现多个客户端之间的同步操作。当客户端请求数据时,服务器可能需要等待某些资源准备好后才能响应,此时可以使用条件变量来等待资源就绪。
(3)、 任务管理:当有多个线程需要执行任务时,可以使用条件变量来通知空闲的线程执行任务。
(4)、 等待输入:当需要等待用户输入时,可以使用条件变量等待用户输入。
总之,当需要等待某些条件满足时,使用条件变量是很常见的一种方式。条件变量提供了一个有效的机制来等待和通知多个线程,以实现共享资源间的同步和互斥。
四、相关的函数API接口
1、初始化条件变量
// 初始化条件变量 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); // 接口说明 返回值:成功返回0,失败返回-1 参数cond:待初始化的条件变量 参数cond_attr:待初始化的条件变量的属性,一般始终为0
2、销毁条件变量
// 销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond); // 接口说明 返回值:成功返回0,失败返回错误码 参数cond:待销毁的条件变量
3、 进入等待队列
// 阻塞等待 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 接口说明 返回值;成功返回0,失败返回-1 参数cond:条件变量 参数mutex:需要获取的互斥锁 // 有限定时间的等待 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); // 接口说明 返回值;成功返回0,失败返回-1 参数cond:条件变量 参数mutex:需要获取的互斥锁 参数abstime:限定的时间
4、唤醒等待队列
// 唤醒全部在条件变量等待队列的线程 int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒一个在条件变量等待队列的线程 int pthread_cond_signal(pthread_cond_t *cond); // 接口说明 返回值:成功返回0,失败返回-1 参数cond:条件变量
五、案例
使用条件变量结合互斥锁完成存钱和取钱的演示
// 条件变量的案例 #include <stdio.h> #include <pthread.h> #include <errno.h> #include <string.h> #include <unistd.h> int money = 0; pthread_mutex_t data_mutex; // 定义互斥锁变量 pthread_once_t data_mutex_once_init; // 函数单例初始化变量 pthread_once_t data_mutex_once_destroy; // 函数单例销毁变量 pthread_cond_t data_cond; // 定义条件变量 pthread_once_t data_cond_once_init; // 函数单例初始化变量 pthread_once_t data_cond_once_destroy; // 函数单例销毁变量 // 初始化互斥锁data_mutex void data_mutex_init(void) { pthread_mutex_init(&data_mutex, NULL); } // 销毁互斥锁data_mutex void data_mutex_destroy(void) { pthread_mutex_destroy(&data_mutex); } // 初始化条件变量data_cond void data_cond_init(void) { pthread_cond_init(&data_cond, NULL); } // 销毁条件变量data_cond void data_cond_destroy(void) { pthread_cond_destroy(&data_cond); } // 线程1的例程函数,用来取钱 void *get_routine(void *arg) { printf("I am recv_routine, my tid = %ld\n", pthread_self()); // 设置线程分离 pthread_detach(pthread_self()); // 函数单例,本程序只会执行data_mutex_init()一次 pthread_once(&data_mutex_once_init, data_mutex_init); // 函数单例,本程序只会执行data_cond_init()一次 pthread_once(&data_cond_once_init, data_cond_init); while(1) { printf("wait mutex to get money...\n"); pthread_mutex_lock(&data_mutex); // 阻塞等待有数据才可以申请成功,用来同步 printf("get mutex to get money\n"); // 判断余额是否大于100 while(money < 100) { printf("get money fail, enter cond queue\n"); // 这里先自动解锁data_mutex,等待被唤醒后,会自动上锁data_mutex pthread_cond_wait(&data_cond, &data_mutex); printf("wake up from cond queue\n"); } money -= 100; printf("get money success\n"); pthread_mutex_unlock(&data_mutex); // 解锁 sleep(1); // 每隔1秒取100元 } // 函数单例,本程序只会执行data_mutex_destroy()一次 pthread_once(&data_mutex_once_destroy, data_mutex_destroy); // 函数单例,本程序只会执行data_cond_destroy()一次 pthread_once(&data_cond_once_destroy, data_cond_destroy); } // 线程2的例程函数,用来存钱 void *give_routine(void *arg) { printf("I am send_routine, my tid = %ld\n", pthread_self()); // 函数单例,本程序只会执行data_mutex_init()一次 pthread_once(&data_mutex_once_init, data_mutex_init); // 函数单例,本程序只会执行data_cond_init()一次 pthread_once(&data_cond_once_init, data_cond_init); while(1) { pthread_mutex_lock(&data_mutex); money += 200; // printf()应该放到临界区外面,但是为了演示效果所以放在这里 printf("current money is %d\n", money); pthread_mutex_unlock(&data_mutex); // 解锁,相当于给线程1发送信号 pthread_cond_broadcast(&data_cond); // 唤醒条件等待队列中等待的线程 sleep(3); // 每隔3秒存200元 } // 函数单例,本程序只会执行data_mutex_destroy()一次 pthread_once(&data_mutex_once_destroy, data_mutex_destroy); // 函数单例,本程序只会执行data_cond_destroy()一次 pthread_once(&data_cond_once_destroy, data_cond_destroy); } int main(int argc, char *argv[]) { pthread_t tid1, tid2; // 创建线程1,用来取钱 errno = pthread_create(&tid1, NULL, get_routine, NULL); if(errno == 0) { printf("pthread create get_routine success, tid = %ld\n", tid1); } else { perror("pthread create get_routine fail\n"); } // 创建线程2,用来存钱,线程拥有分离属性 errno = pthread_create(&tid2, NULL, give_routine, NULL); if(errno == 0) { printf("pthread create give_routine success, tid = %ld\n", tid2); } else { perror("pthread create give_routine fail\n"); } // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出 // 或者加上while(1)等让主函数不退出 pthread_exit(0); return 0; }
六、总结
条件变量通常用于多线程间共享资源的同步访问,一般要配合互斥锁来使用,可以结合案例来加深对条件变量的理解。