目录
前言
在《线程同步》这篇文章里讲过条件变量实现线程同步,但是只是模糊地讲了一下条件变量函数的基本用法,这篇文章将详细讲解一下条件变量的wait函数。看过之前那篇文章的可以直接跳转到条件变量的wait函数
回顾
条件变量
条件变量操作函数
pthread_cond_t cond; // 生命条件变量
int pthread_cond_init(); // 初始化条件变量
int pthread_cond_destroy(); // 销毁条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 声明并初始化
int pthread_cond_wait(); // 等待被唤醒
int pthread_cond_timedwait(); // 等待被唤醒,带超时机制
int pthread_cond_signal(); // 唤醒一个等待中的线程
int pthread_cond_broadcast(); // 唤醒全部等待中的线程
// 设置条件变量的共享属性,也就是在多个进程的线程之间是否共享条件变量
int pthread_condattr_getpshared(); // 获取共享属性
int pthread_condattr_setpshared(); // 设置共享属性
// 设置条件变量的时钟属性,一般用不到
int pthread_condattr_getclock(); // 获取时钟属性
int pthread_condattr_setclock(); // 设置时钟属性
注意事项
首先大家不要被条件变量的名称给误导了,大家就把它当作是一种特殊的锁就行了
另外条件变量必须搭配互斥锁使用,至于为什么以后会讲,大家也可以先猜测猜测是为什么
条件变量的wait()函数会产生阻塞
我们接下来要讲的就是
int pthread_cond_wait(); // 等待被唤醒
这个函数
条件变量的wait函数
pthread_cond_wait(&cond,&mutex)
首先可以看到它有两个参数,一个是条件变量,一个是互斥锁,这是系统的设定。至于为什么下面会讲。
该函数有三个动作:
1、把互斥锁解锁
2、阻塞、等待条件信号(被唤醒)
3、条件被触发+给互斥锁加锁(这两个操作是原子性的)
demo程序验证:
demo13.cpp:
// 本程序用于验证条件变量wait函数发生了什么
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 声明并初始化互斥锁
void *thmain1(void *arg); // 线程1主函数
void *thmain2(void *arg); // 线程2主函数
int main(int argc, char* argv[])
{
pthread_t thid1,thid2; // 线程1、2的id
// 创建线程
if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1); // 延时一秒错开线程运行
if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; }
// 等待子线程退出
pthread_join(thid1,NULL); pthread_join(thid2,NULL);
// 退出前销毁条件变量与互斥锁
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
void *thmain1(void *arg)
{
printf("线程1开始申请互斥锁...\n");
pthread_mutex_lock(&mutex);
printf("线程1申请互斥锁成功...\n\n");
sleep(5);
printf("线程1开始等待条件信号...\n");
pthread_cond_wait(&cond,&mutex);
printf("线程1等待条件信号成功...\n\n");
}
void *thmain2(void *arg)
{
printf("线程2开始申请互斥锁...\n");
pthread_mutex_lock(&mutex);
printf("线程2申请互斥锁成功...\n\n");
printf("线程2开始等待条件信号...\n");
pthread_cond_wait(&cond,&mutex);
printf("线程2等待条件信号成功...\n\n");
}
解释一下代码,首先申请互斥锁和条件变量,因为条件变量必须搭配互斥锁。然后线程1创建1秒后线程2才创建,这样的话,线程1就会先运行线程主函数获得互斥锁。在线程1获取互斥锁一秒之后,线程2开始申请,这个时候是肯定不成功的。再过4秒之后,线程1开始等待条件信号,注意!这个时候如果线程2申请互斥锁成功了,就代表条件变量的wait函数释放了锁。
makfile文件如下:
demo13:demo13.cpp
g++ -g -o demo13 demo13.cpp -lpthread
编译运行
可以看到线程1开始等待条件信号之后线程2的互斥锁就申请成功了。
这证明了条件变量wait函数的前两个观点
第二个观点运行的时候你会发现wait函数确实产生了阻塞,因为没有输出“等待条件信号成功”的字样 。
下面证明条件出发和加锁是原子性的。
demo14.cpp:
// 本程序用于验证条件变量wait函数发生了什么
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 声明并初始化条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 声明并初始化互斥锁
void *thmain1(void *arg); // 线程1主函数
void *thmain2(void *arg); // 线程2主函数
int main(int argc, char* argv[])
{
pthread_t thid1,thid2; // 线程1、2的id
// 创建线程
if(pthread_create(&thid1,0,thmain1,0)!=0) { printf("create failed.\n"); return -1; }
sleep(1); // 延时一秒错开线程运行
if(pthread_create(&thid2,0,thmain2,0)!=0) { printf("create failed.\n"); return -1; }
// 等待子线程退出
pthread_join(thid1,NULL); pthread_join(thid2,NULL);
// 退出前销毁条件变量与互斥锁
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
void *thmain1(void *arg)
{
printf("线程1开始申请互斥锁...\n");
pthread_mutex_lock(&mutex);
printf("线程1申请互斥锁成功...\n\n");
printf("线程1开始等待条件信号...\n");
pthread_cond_wait(&cond,&mutex);
printf("线程1等待条件信号成功...\n\n");
}
void *thmain2(void *arg)
{
printf("线程2开始申请互斥锁...\n");
pthread_mutex_lock(&mutex); // 线程1等待条件信号的时候,线程2申请锁肯定会成功
printf("线程2申请互斥锁成功...\n\n");
printf("发送条件信号...\n");
pthread_cond_signal(&cond); // 线程2申请锁成功之后给线程1发送条件信号
sleep(5);
printf("线程2解锁...\n");
pthread_mutex_unlock(&mutex);
}
代码是这样的:首先线程1获取锁,然后又调用条件变量wait函数,这个时候会解锁,所以线程2申请锁肯定会成功。线程2申请锁成功之后,给线程1发送条件信号,这个时候的wait函数收到了条件信号是立即返回呢?还是说等线程2解锁了之后wait函数才返回呢?如果当线程2发送条件信号之后线程1的wait函数就继续往下运行,则说明观点三并不是原子性的。如果线程2发送条件信号之后再解锁这个时候wait函数才继续往下运行,则说明wait函数收到条件信号和加锁是原子性操作。
makefile不变
编译运行:
可以看到是线程2解锁了之后,条件变量的wait函数才返回,不然就会一直阻塞。所以观点三原子性成立。
为什么条件变量一定要搭配互斥锁使用?
首先,这是程序的设定,但是程序的设定一定有他自己的道理,我认为是这样的:首先我们从条件变量的使用就可以知道,他一定是需要用互斥锁的,但是我们之前讲过的互斥锁有两个,一个mutex互斥锁,一个spinlock自旋锁。
那她为什么选择了mutex互斥锁而没有选择spinlock自旋锁呢?只要你看过我的《线程同步》这篇文章应该就能知道。我们说过互斥锁在等待锁的过程中会休眠,不会消耗cpu,但是自旋锁在等待的过程中会不断地判断锁是否可用,是很消耗cpu的。而在条件变量的wait函数等待条件信号的这个过程中,时间是很长的,在生产消费者模型中,如果生产者不生产内容,wait函数就不会收到条件信号,这样的话如果使用spinlock就会消耗大量的cpu,但是mutex不会,因为它休眠了。
总结
从上面的文章我们就很清楚条件变量的wait函数了,所以以后使用这个函数的时候一定要谨慎,要知道它会把互斥锁解开、会产生阻塞、并且收到信号和收回锁必须是原子性的。
结语
还是和以前一样,文章有错的话,请大佬一定指出!谢谢大家!