条件变量变量也是出自POSIX线程标准,另一种线程同步机制。主要用来等待某个条件的发生。可以用来同步同一进程中的各个线程。当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来进行进程间的同步。

每个条件变量总是和一个互斥量相关联,条件本身是由互斥量保护的,线程在改变条件状态之间必须要锁住互斥量。条件变量相对于互斥量最大的优点在于允许线程以无竞争的方式等待条件的发生。当一个线程获得互斥锁后,发现自己需要等待某个条件变为真,如果是这样,该线程就可以等待在某个条件上,这样就不需要通过轮询的方式来判断添加,大大节省了CPU时间。

在互斥量一文中说过:互斥量是用于上锁,而不是用于等待;现在这句话可以加强为:互斥量是用于上锁,条件变量用于等待

条件变量声明为pthread_cond_t数据类型,在<bits/pthreadtypes.h>中有具体的定义。

1条件变量初始化和销毁

#include <pthread.h>
int pthread_cond_init (pthread_cond_t *cond,const pthread_condattr_t *cond_attr) ;
int pthread_cond_destroy (pthread_cond_t *cond);
两者的返回值都是:若成功则返回0,否则返回错误号

上面两个函数分别由于条件变量的初始化和销毁。

和互斥量的初始化一样,如果条件变量是静态分配的,可以通过常量进行初始化,如下:

pthread_cond_t mlock = PTHREAD_COND_INITIALIZER;

也可以通过pthread_cond_init()进行初始化,对于动态分配的条件变量由于不能直接赋值进行初始化,就只能采用这种方式进行初始化。那么当不在需要使用条件变量,释放底层空间之前,需要调用pthread_cond_destroy()销毁该条件所占用的资源。

2条件变量的使用

/*  等待条件变为真 */
int pthread_cond_wait (pthread_cond_t *cond,pthread_mutex_t *mutex); /* 限时等待条件为真 */
int pthread_cond_timedwait (pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime); /* 唤醒一个等待条件的线程. */
int pthread_cond_signal (pthread_cond_t *cond);  /* 唤醒等待该条件的所有线程 */
int pthread_cond_broadcast (pthread_cond_t *cond);

(1)pthread_cond_wait()函数用于等待条件被触发。该函数传入两个参数,一个条件变量一个互斥量,函数将条件变量和互斥量进行关联,互斥量对该条件进行保护,传入的互斥量必须是已经锁住的。调用pthread_cond_wait()函数后,会原子的执行以下两个动作:

  • 将调用线程放到等待条件的线程列表上,即进入睡眠;
  • 对互斥量进行解锁;

由于这两个操作时原子操作,这样就关闭了条件检查和线程进入睡眠等待条件改变这两个操作之间的时间通道,这样就不会错过任何条件的变化。

当pthread_cond_wait()返回后,互斥量会再次被锁住。

(2)pthread_cond_timedwait()函数和pthread_cond_wait()的工作方式相似,只是多了一个等待时间。等待时间的结构为struct timespec:

struct timespec{
time_t tv_sec //Seconds.
long tv_nsec //Nanoseconds.
};

函数要求传入的时间值是一个绝对值,不是相对值,例如,想要等待3分钟,必须先获得当前时间,然后加上3分钟。

要想获得当前系统时间的timespec值,没有直接可调用的函数,需要通过调用gettimeofday函数获取timeval结构,然后转换成timespec结构,转换公式就是:

timeSpec.tv_sec = timeVal.tv_sec;
timeSpec.tv_nsec = timeVal.tv_usec * 1000;

所以要等待3分钟,timespec时间结构的获得应该如下所示:

struct timeval now;
struct timespec until;
gettimeofday(&now);//获得系统当前时间 //把时间从timeval结构转换成timespec结构
until.tv_sec = now.tv_sec;
until.tv_nsec = now.tv_usec * 1000; //增加min
until.tv_sec += 3 * 60;

如果时间到后,条件还没有发生,那么会返回ETIMEDOUT错误。

     从pthread_cond_wait()和pthread_cond_timewait()成功返回时,线程需要重新计算条件,因为其他线程可能在运行过程中已经改变条件。

注意:pthread_cond_wait/pthread_cond_timewait函数的返回并不意味着条件的值一定发生了改变,必须重新检查条件的值。

pthread_cond_wait/pthread_cond_timewait函数返回时,相应的互斥量将被当前线程锁定。即使是函数出错返回。

一般一个条件表达式都是在一个互斥量的保护下被检查的。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或者所有线程被唤醒,接着都试图再次占有相应的互斥量。

阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait函数返回之前,条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥量之前,必须重新测试条件变量。最后的测试方式是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。例如:

pthread_mutex_lock();

while (condition_is_false)

 pthread_cond_wait();

pthread_mutex_unlock();

注意:pthread_cond_wait函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥量仍将处在锁定状态。(即pthread_cond_wait失败返回,但仍然获得互斥量,在清理函数注意解开该互斥量

(3)pthread_cond_signal() & pthread_cond_broadcast()

这两个函数都是用于向等待条件的线程发送唤醒信号,pthread_cond_signal()函数只会唤醒等待该条件的某个线程,pthread_cond_broadcast()会广播条件状态的改变,以唤醒等待该条件的所有线程。例如多个线程只读共享资源,这是可以将它们都唤醒。

这里要注意的是:一定要在改变条件状态后,再给线程发送信号。

考虑条件变量信号单播发送和广播发送的一种候选方式是坚持使用广播发送。只有在等待者代码编写确切,只有一个等待者需要唤醒,且唤醒哪个线程无所谓,那么此时为这种情况使用单播,所以其他情况下都必须使用广播发送。

唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。

3唤醒丢失问题

    
     在线程未获得相应的互斥量时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
     唤醒丢失往往会在下面的情况下发生:
  • 一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
  • 另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
  • 没有线程正在处在阻塞等待的状态下。

4条件变量的属性设置

/* 初始化条件变量属性对象  */
int pthread_condattr_init (pthread_condattr_t *attr); /* 销毁条件变量属性对象 */
int pthread_condattr_destroy (pthread_condattr_t *attr); /* 获取条件变量属性对象在进程间共享与否的标识 */
int pthread_condattr_getpshared (const pthread_condattr_t *attr,int *pshared); /* 设置条件变量属性对象,标识在进程间共享与否 */
int pthread_condattr_setpshared (pthread_condattr_t *attr, int pshared) ;
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h> pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count = 0; void *decrement_count(void *arg)
{
pthread_mutex_lock(&count_lock);
printf("decrement_count get count_lock\n");
while(count == 0)
{
printf("decrement_count count == 0 \n");
printf("decrement_count before cond_wait \n");
pthread_cond_wait(&count_nonzero, &count_lock);
printf("decrement_count after cond_wait \n");
printf("decrement_count count = %d \n",count);
} count = count + 1;
pthread_mutex_unlock(&count_lock);
} void *increment_count(void *arg)
{
pthread_mutex_lock(&count_lock);
printf("increment_count get count_lock \n");
if(count == 0)
{
printf("increment_count before cond_signal \n");
pthread_cond_signal(&count_nonzero);
printf("increment_count after cond_signal \n");
} count = count + 1;
printf("huangcheng \n");
printf("increment_count count = %d \n",count);
pthread_mutex_unlock(&count_lock);
} int main(void)
{
pthread_t tid1, tid2; pthread_mutex_init(&count_lock, NULL);
pthread_cond_init(&count_nonzero, NULL); pthread_create(&tid1, NULL, decrement_count, NULL);
sleep(2);
pthread_create(&tid2, NULL, increment_count, NULL); sleep(10);
pthread_exit(0); return 0;
}

运行结果:

huangcheng@ubuntu:~$ ./a.out
decrement_count get count_lock
decrement_count count == 0
decrement_count before cond_wait
increment_count get count_lock
increment_count before cond_signal
increment_count after cond_signal
huangcheng
increment_count count = 1
decrement_count after cond_wait
decrement_count count = 1
huangcheng@ubuntu:~$

说明:
等待线程
1.使用pthread_cond_wait前要先加锁
2.pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
3.pthread_cond_wait被激活后会再自动加锁

激活线程:
1.加锁(和等待线程用同一个锁)
2.pthread_cond_signal发送信号
3.解锁
激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。

即执行顺序:
等待线程->使用pthread_cond_wait前要先加锁
等待线程->pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
激活线程->加锁(和等待线程用同一个锁)
激活线程->pthread_cond_signal发送信号
激活线程->解锁
等待线程->pthread_cond_wait被激活后会再自动加锁

UNIX环境高级编程——线程同步之条件变量以及属性的更多相关文章

  1. UNIX环境高级编程——线程同步之读写锁以及属性

    读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程.当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步, 互 ...

  2. UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)

    一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...

  3. UNIX环境高级编程——线程同步之互斥量

    互斥量(也称为互斥锁)出自POSIX线程标准,可以用来同步同一进程中的各个线程.当然如果一个互斥量存放在多个进程共享的某个内存区中,那么还可以通过互斥量来进行进程间的同步. 互斥量,从字面上就可以知道 ...

  4. UNIX环境高级编程——线程属性

    pthread_attr_t 的缺省属性值 属性 值 结果 scope PTHREAD_SCOPE_PROCESS 新线程与进程中的其他线程发生竞争. detachstate PTHREAD_CREA ...

  5. Unix 环境高级编程---线程创建、同步、

    一下代码主要实现了linux下线程创建的基本方法,这些都是使用默认属性的.以后有机会再探讨自定义属性的情况.主要是为了练习三种基本的线程同步方法:互斥.读写锁以及条件变量. #include < ...

  6. UNIX环境高级编程——线程和fork

    当线程调用fork时,就为子进程创建了整个进程地址空间的副本.子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量.读写锁和条件变量的状态.如果父进程包含多个线程,子进程在fork返回以后 ...

  7. UNIX环境高级编程——线程

    线程包含了表示进程内执行环境必需的信息,其中包括进程中标示线程的线程ID.一组寄存器值.栈.调度优先级和策略.信号屏蔽字.errno变量以及线程私有数据. 进程的所有信息对该进程的所有线程都是共享的, ...

  8. UNIX环境高级编程——线程和信号

    每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的.这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变.这样如果一 ...

  9. UNIX环境高级编程——线程私有数据

    线程私有数据(Thread-specific data,TSD):存储和查询与某个线程相关数据的一种机制. 在进程内的所有线程都共享相同的地址空间,即意味着任何声明为静态或外部变量,或在进程堆声明的变 ...

随机推荐

  1. 选取id不为sth的div元素

    选取id不为sth的div元素$("div:not(#sth)")

  2. COCO 数据集的使用

    Windows 10 编译 Pycocotools 踩坑记 COCO数据库简介 微软发布的COCO数据库, 除了图片以外还提供物体检测, 分割(segmentation)和对图像的语义文本描述信息. ...

  3. 代码之间-论文修改助手v1.0版本发布

    论文查重,是每个毕业生都要面临的一个令人头疼的问题,如果写论文不认真,很可能导致查重红一大片. 之前有帮助一些朋友修改论文降低重复率,做了一些工作后发现,国内的查重机构,如知网.维普等,大多数是基于关 ...

  4. python笔记十三(高阶函数、装饰器)

    一.高阶函数 函数只要有以下两个特征中一个就可以称为高阶函数: a:函数名作为一个实参传入另一个函数中 b:函数的返回值中包含函数名 下面我们用代码来感受一下这两种形式: import time # ...

  5. UIkit复习:UIContorl及子控件的剖析

    1.模块继承关系: 1.UIButton        ->UIControl  -> UIView 2.UILabel          ->UIview 3.UIImageVie ...

  6. Docker 编辑网络配置文件

    Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts, /etc/hostname 和 /etc/resolve.conf 文件. 但是这些修改是临时的,只在运行的容器中保留, ...

  7. 【Java 语言】Java 多线程 一 ( 线程启动 | 线程中断 )

    一. 线程启动 线程启动 : -- 1. 继承 Thread 运行线程 : 重写 Thread 类的 run 方法, 然后执行该线程; -- 2. 实现 Runnable 接口, 并运行线程; -- ...

  8. Bootstrap3 栅格系统-实例:响应列重置(Responsive column resets)

    四层的网格你肯定会遇到问题,可用在特定的断点,你的列不清楚作为一个比另一个高完全正确.为了解决这个问题,结合使用.clearfix和响应的实用工具类. <div class="row& ...

  9. JBOSS EAP实战(1)

    JBOSS的诞生 1998年,在硅谷SUN公司的SAP实验室,一个年轻人正坐在电脑前面思考,然后写着什么东西.不,他没有在写程序,他在写辞呈.他正在做出人生的一个重大决定:他要辞掉在SUN的这份工作, ...

  10. NuGet包断线续传下载

    NuGet包断线续传下载(金庆的专栏)NuGet是VC的扩展,用来下载依赖包.NuGet下载没有断线续传,下载源又很容易断开.  https://nuget.org/api/v2/  https:// ...