时间紧可以跳过前面的介绍,后面的实例代码详解才是重点。
一、什么是条件变量
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
条 件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件 变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
使用条件变量之前要先进行初始化。可以在单个语句中生成和初始化一个条件变量如:
pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用于进程间线程的通信)。
也可以利用函数pthread_cond_init动态初始化。
二、条件变量函数
1.
名称:
pthread_cond_init
目标:
条件变量初始化
头文件:
#include < pthread.h>
函数原形:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
参数:
cond 条件变量
attr 条件变量属性
返回值:
成功返回0,出错返回错误编号。
pthread_cond_init函数可以用来初始化一个条件变量。他使用变量attr所指定的属性来初始化一个条件变量,如果参数attr为空,那么它将使用缺省的属性来设置所指定的条件变量。
2.
名称:
pthread_cond_destroy
目标:
条件变量摧毁
头文件:
#include < pthread.h>
函数原形:
int pthread_cond_destroy(pthread_cond_t *cond);
参数:
cond 条件变量
返回值:
成功返回0,出错返回错误编号。
pthread_cond_destroy函数可以用来摧毁所指定的条件变量,同时将会释放所给它分配的资源。调用该函数的进程也并不要求等待在参数所指定的条件变量上。
3.
名称:
pthread_cond_wait/pthread_cond_timedwait
目标:
条件变量等待
头文件:
#include < pthread.h>
函数原形:
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct timespec *abstime);
参数:
cond 条件变量
mutex 互斥锁
返回值:
成功返回0,出错返回错误编号。
第一个参数*cond是指向一个条件变量的指针。第二个参数*mutex则是对相关的互斥锁的指针。函数pthread_cond_timedwait函数类型与函数pthread_cond_wait,区别在于,如果达到或是超过所引用的参数*abstime,它将结束并返回错误ETIME.pthread_cond_timedwait函数的参数*abstime指向一个timespec结构。该结构如下:
typedef struct timespec{
time_t tv_sec;
long tv_nsex;
}timespec_t;
4.
名称:
pthread_cond_signal/pthread_cond_broadcast
目标:
条件变量通知
头文件:
#include < pthread.h>
函数原形:
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
参数:
cond 条件变量
返回值:
成功返回0,出错返回错误编号。
参数*cond是对类型为pthread_cond_t 的一个条件变量的指针。当调用pthread_cond_signal时一个在相同条件变量上阻塞的线程将被解锁。如果同时有多个线程阻塞,则由调度策略确定接收通知的线程。如果调用pthread_cond_broadcast,则将通知阻塞在这个条件变量上的所有线程。一旦被唤醒,线程仍然会要求互斥锁。如果当前没有线程等待通知,则上面两种调用实际上成为一个空操作。如果参数*cond指向非法地址,则返回值EINVAL。
下面是一个简单的例子,我们可以从程序的运行来了解条件变量的作用。
/*
*程序创建了2个新线程使他们同步运行,实现进程t_b打印10以内3的倍数,t_a打印其他的数,程序开始线程t_b不满足条件等待,
* 线程t_a运行使a循环加1并打印。直到i为3的倍数时,线程t_a发送信号通知进程t_b,这时t_b满足条件,打印i值。
*********************************************************************************************************
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*静态初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
void *thread1(void *);
void *thread2(void *);
int i=1;
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread2,(void *)NULL);/*创建进程t_a*/
pthread_create(&t_b,NULL,thread1,(void *)NULL); /*创建进程t_b*/
pthread_join(t_b, NULL);/*等待进程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1;i<=9;i++)
{
pthread_mutex_lock(&mutex);/*锁住互斥量*/
if(i%3==0)
{
pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/
sleep(1);
printf("已唤醒thread2\n");
}
else
{
printf("thread1:%d\n",i);
}
pthread_mutex_unlock(&mutex);/*解锁互斥量*/
printf("thread1 unlock\n");
sleep(1);
}
}
void *thread2(void *junk)
{
while(i<9)
{
pthread_mutex_lock(&mutex);
sleep(1);
printf("thread2 get lock\n");
if(i%3!=0)
{
printf("开始开始等待\n");
pthread_cond_wait(&cond,&mutex);/*等待*/
printf("结束等待\n");
printf("thread2:%d\n",i);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
下面是运行结果:
#cc –lpthread –o cond cond.c
#./cond
1,thread2 get lock thread2获得锁
2,开始开始等待 pthread_cond_wait解锁,再阻塞
3,thread1:1 thread1获得锁
4,thread1 unlock thread1释放锁
5,thread1:2 thread2阻塞中不会竞争锁,thread1再次获得锁
6,thread1 unlock thread1释放锁
7,已唤醒thread2 此时i=3,唤醒thread2,thread2进入就绪状态
8,thread1 unlock thread1释放锁
9,结束等待 thread2获得锁退出阻塞,执行pthread_cond_wait后的代码
10,thread2:3 打印这句后thread2解锁
11,thread2 get lock thread2获得锁, <为什么此时不是thread1获得锁,为了让thread1获得锁,我在thread2解锁后还sleep(1)了,但还是 thread2获得锁>
12,开始开始等待 pthread_cond_wait解锁,再阻塞
13,thread1:4 。。。
14,thread1 unlock 。。。
15,thread1:5
16,thread1 unlock
17,已唤醒thread2
18,thread1 unlock
19,结束等待
20,thread2:6
21,thread1:7
22,thread1 unlock
23,thread2 get lock
24,开始开始等待
25,thread1:8
26,thread1 unlock
27,已唤醒thread2
28,thread1 unlock
29,结束等待
30,thread2:9
1~30的标号不是打印信息哈
结论:
pthread_cond_wait() 必须与pthread_mutex_lock() 配套使用。pthread_cond_wait()函数一进入wait状态就会自动解锁(有原子操作,先解锁,再阻塞)。当其他线程(线程乙)通过pthread_cond_signal()或pthread_cond_broadcast,把该线程(线程甲)唤醒,使pthread_cond_wait()进入就绪状态
1,如果此时发信号pthread_cond_signal()的线程乙已经释放锁,线程甲自动获得该mutex,继续执行pthread_cond_wait()后的代码。
2,如果此时发信号pthread_cond_signal()的线程乙还没有释放锁,则线程甲继续保持就绪状态直到线程乙释放锁后,线程甲再获得mutex
程序详解参考如下链接
https://weihe6666.iteye.com/blog/1170141
但这个链接说
pthread_cond_signal(&cond)有原子操作(先解锁,再发送信号)????
问题:
①,上述结论是我根据执行结果做的猜想,真实情况是否是我猜想的这样?
②,我用上述的方法是否可以证明pthread_cond_signal(&cond)没有原子操作,并不会解锁,只是发信号????
③,执行结果的11步为什么不是thread1获得锁?
三天后找到答案:
我的操作系统:CentOS7
①,
根据UNIX网络编程(卷2)第七章得:上述猜想基本正确,pthread_cond_wait()函数确定有原子操作,pthread_cond_signal(&cond)没有原子操作,于是为了避免上锁冲突,可以在当前线程里先解锁,再调用pthread_cond_signal( )函数发送信号。
书中介绍了一种上锁冲突的情况:
先调pthread_cond_signal(&cond )再释放锁,当该条件变量被发送信号后,系统立即调度正在等待中的线程,该线程开始运行,但立即停止,因为它没能获取相应的互斥锁。
②,
执行结果的11步为什么不是thread1获得锁,为了让thread1获得锁,我在thread2解锁后还sleep(1)了
根据UNIX网络编程(卷2)第七章得:在多线程的程序中,主程序里在创建多线程前应该调用pthread_setconcurrency(nthreads)函数,保证生成的线程都有执行机会,该函数输入参数为子线程个数,我在本例代码的主线程添加这个函数后,第11步就变成了thread1先获得锁。
同时对于多线程编程,主线程在创建线程后应该调用pthread_join( )函数,等待子线程执行完后再结束主线程。
③,
根据书上7.3节生产者-消费者代码例程,我对其编译运行测试后,发现当A为13万时,线程一和线程二各执行了一部分次数,当A小于13万时,线程一执行A次,线程二不执行,得出一个新猜想:for循环与while循环的切换速度,是不同线程间竞争获取到锁这个速度的13万倍左右。
主要代码逻辑
主程序逻辑:设置一个总的for循环次数A,如果线程一执行了A次,则线程二不执行,最终使得两线程总共执行for循环次数相加为A
线程一:
for循环/while循环
{
pthread_mutex_lock(&mutex);
....
pthread_mutex_unlock(&mutex);
}
线程二:
for循环/while循环
{
pthread_mutex_lock(&mutex);
....
pthread_mutex_unlock(&mutex);
}