与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。条件变量使我们可以睡眠等待某种条件出现。
条件变量是利用线程间共享的全局变量进行同步的一种机制,
主要包括两个动作:
一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

条件的检测是在互斥锁的保护下进行的。如果条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。

pthread_cond_wait 原子调用: 等待条件变量, 解除锁, 然后阻塞 
当 pthread_cond_wait 返回,则条件变量有信号,同时上锁

等待条件有两种方式:
条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(), 
其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait() 
(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。

mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP), 
且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前, 
mutex保持锁定状态,并在线程挂起进入等待前解锁。 在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,
以与进入pthread_cond_wait()前的加锁动作对应。

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个; 
而pthread_cond_broadcast()则激活所有等待线程(惊群)。

1 简介
当多个线程之间因为存在某种依赖关系,导致只有当某个条件存在时,才可以执行某个线程,此时条件变量(pthread_cond_t)可以派上用场。
例1: 当系统不忙(这是一个条件)时,执行扫描文件状态的线程。
例2: 多个线程组成线程池,只有当任务队列中存在任务时,才用其中一个线程去执行这个任务。为避免惊群(thrundering herd),可以采用条件变量同步线程池中的线程。

2 用法
条件变量(pthread_cond_t)必须与锁(pthread_mutex_t)一起使用。

条件变量的API:

1) pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
使用 cond_attr 指定的属性初始化条件变量 cond,当 cond_attr 为 NULL 时,使用缺省的属性。LinuxThreads 实现条件变量不支持属性,
因此 cond_attr 参数实际被忽略。pthread_cond_t 类型的变量也可以用 PTHREAD_COND_INITIALIZER 常量进行静态初始化。

2) int pthread_cond_signal(pthread_cond_t *cond); / int pthread_cond_broadcast(pthread_cond_t *cond);
调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。
pthread_cond_signal使在条件变量上等待的线程中的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动。
在多处理器上,该函数是可能同时唤醒多个线程
pthread_cond_broadcast 重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。

3) 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);
pthread_cond_wait 自动解锁互斥量(//如果不释放,其他线程永远改变不了条件,条件永不能为真,该线程将一直阻塞下去)(如同执行了 pthread_unlock_mutex),
并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait 之前,应用程序必须加锁互斥量。
pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了 pthread_lock_mutex)。即条件变量触发--->(如果互斥锁被其他线程加锁,阻塞等到解锁)对互斥量加锁---->然后返回。(可能不是十分正确)
互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,
这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。

pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间
如果当前没有线程等待通知,则上面两种调用实际上成为一个空操作。

4) int pthread_cond_destroy(pthread_cond_t *cond);
销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。否则返回EBUSY
在 LinuxThreads 的实现中,条件变量不联结资源,除检查有没有等待的线程外,pthread_cond_destroy 实际上什么也不做。

3. 取消
pthread_cond_wait 和 pthread_cond_timedwait 是取消点。如果一个线程在这些函数上挂起时被取消,线程立即继续执行,
然后再次对 pthread_cond_wait 和 pthread_cond_timedwait 在 mutex 参数加锁,最后执行取消。因此,当调用清除处理程序时,可确保,mutex 是加锁的。

4. 异步信号安全(Async-signal Safety)

条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用
pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁。

5 避免惊群
每当一块肉丢到狼群,就引发一群狼去争抢,但最后只有一只狼得到了肉。
计算机的惊群会造成服务器资源空耗。
pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.
如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
但使用pthread_cond_signal不会有“惊群现象”产生,它最多只给一个线程发信号。
假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。
如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。

6、pthread_cond_signal的位置
In Thread1:

pthread_mutex_lock(&m_mutex);
pthread_cond_wait(&m_cond,&m_mutex);
pthread_mutex_unlock(&m_mutex);

In Thread2:

pthread_mutex_lock(&m_mutex);
pthread_cond_signal(&m_cond);
pthread_mutex_unlock(&m_mutex);

为什么要与pthread_mutex 一起使用呢? 这是为了应对 线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候,此时线程2调用了 cond_singal 的情况。
如果不用mutex锁的话,这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 释放锁并进入wait_cond状态 ,此时线程2上锁)
的时候才能调用cond_singal.

pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,但是各有有缺点。

之间:
pthread_mutex_lock
xxxxxxx
pthread_cond_signal
pthread_mutex_unlock
缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的 行为),所以一来一回会有性能的问题。
但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列,
cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。

之后:
pthread_mutex_lock
xxxxxxx
pthread_mutex_unlock
pthread_cond_signal
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。

7、唤醒丢失问题
在线程并没有阻塞在条件变量上时,调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。

唤醒丢失往往会在下面的情况下发生:

一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
没有线程正在处在阻塞等待的状态下。

-----------------------------------------------------------------------------

取消点的问题:

pthread_cond_wait 和pthread_cond_timedwait都被实现为取消点。因此,线程被取消之后,在该处等待的线程将立即重新运行,在重新锁定mutex后wait函数返回,然后执行取消动作。

也就是说,如果wait函数被取消,mutex将依然保持锁定状态,那么线程需要定义退出回调函数来为其解锁。

使用pthread_cleanup_push() 和 pthread_cleanup_pop设置回调函数保护。

线程同步,条件变量pthread_cond_wait的更多相关文章

  1. pThreads线程(三) 线程同步--条件变量

    条件变量(Condition Variables) 参考资料:http://game-lab.org/posts/posix-thread-cn/#5.1 条件变量是什么? 条件变量为我们提供了另一种 ...

  2. Linux线程同步——条件变量

    互斥锁是用来给资源上锁的,而条件变量是用来等待而不是用来上锁的. 条件变量用来自动阻塞一个线程,直到某特殊情况发生为止. 通常条件变量和互斥锁同时使用. 和条件变量使用有关的几个重要函数: int p ...

  3. linux Posix线程同步(条件变量) 实例

    条件变量:与互斥量一起使用,暂时申请不到某资源时进入条件阻塞等待,当资源具备时线程恢复运行 应用场合:生产线程不断的生产资源,并通知产生资源的条件,消费线程在没有资源情况下进入条件等待,一直等到条件信 ...

  4. 条件变量pthread_cond_wait()和pthread_cond_signal()详解

    条件变量        条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起:另一个线程使"条件成立" ...

  5. Java线程:条件变量、原子量、线程池等

    一.条件变量 条件变量实现了java.util.concurrent.locks.Condition接口,条件变量的实例化就是通过一个Lock对象上调用newCondition()方法获得的,这样条件 ...

  6. python线程的条件变量Condition的用法实例

      Condition 对象就是条件变量,它总是与某种锁相关联,可以是外部传入的锁或是系统默认创建的锁.当几个条件变量共享一个锁时,你就应该自己传入一个锁.这个锁不需要你操心,Condition 类会 ...

  7. Linux:条件变量

    条件变量:     条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数:     pthread_cond_init函数     pthrea ...

  8. 线程同步、信号量、system v IPC

    一.线程同步 条件变量 什么是条件变量? 线程A等待某个条件成立,条件成立,线程A才继续向下执行.线程B的执行使条件成立,条件成立以后唤醒线程A,以继续执行.这个条件就是条件变量. pthread_c ...

  9. linux系统编程--线程同步

    同步概念 所谓同步,即同时起步,协调一致.不同的对象,对“同步”的理解方式略有不同. 如,设备同步,是指在两个设备之间规定一个共同的时间参考: 数据库同步,是指让两个或多个数据库内容保持一致,或者按需 ...

随机推荐

  1. centos下nginx安装与配置

    nginx依赖以下模块: l  gzip模块需要 zlib 库 l  rewrite模块需要 pcre 库 l  ssl 功能需要openssl库 tar xzvf nginx-1.9.15.tar. ...

  2. django session 使用案例

    django session 使用案例 HTTP被设计为”无状态”,每次请求都处于相同的空间中. 在一次请求和下一次请求之间没有任何状态保持,我们无法根据请求的任何方面(IP地址,用户代理等)来识别来 ...

  3. 2018-2019-1 20189218《Linux内核原理与分析》第八周作业

    编译链接的过程 编译就是把文本形式源代码翻译为机器语言形式的目标文件过程. 链接是把目标文件.操作系统的启动代码和用到的库文件进行组织最终形成可执行代码的过程. 对于GCC来说,编译源代码并最终形成可 ...

  4. Duilib 实现开关按钮

    转载:http://blog.csdn.net/wuan584974722/article/details/25045737 我们在做MFC程序时候经常会一个切换式的按钮,之前我的做法是利用butti ...

  5. ubuntu16.04下编译ceres-solver

    一.编译环境 ubuntu16.04 二.准备工作之安装必要的库 2.1安装cmake sudo apt-get install cmake 2.2 安装google-glog + gflags su ...

  6. ActiveMQ、RabbitMQ、RocketMQ、Kafka 对比(图示)

    RabbitMQ 和 Kafka 对比,一篇好的介绍文章:https://my.oschina.net/u/236698/blog/501834 ActiveMQ.RabbitMQ.RocketMQ. ...

  7. 【集群搭建】Zookeeper集群环境配置

    1.下载解压安装文件 2.配置文件:conf/zoo.cfg tickTime=2000 dataDir=/usr/sunny/logs/zookeeper/data dataLogDir=/usr/ ...

  8. OJ上 编译器 G++和C++的区别

    原文 :http://blog.csdn.net/febr2/article/details/52068357 编译时的差异: 编译器优化不同: 举个栗子: ①: a++ ②: ++a 从标准C的角度 ...

  9. Vue开发中的中央事件总线

    在Vue开发中会遇到大量的组件之间共享数据的情形,针对不同的情形,Vue有相对应的解决方案.比如,父组件向子组件传值可以使用props,复杂项目中不同模块之间传值可以使用Vuex.但是,对于一些简单的 ...

  10. ajax用beforeSend自定义请求过程中客户端事件,提高用户体验

    本文为博主原创,未经允许不得转载: 在应用ajax的过程中,当我们再前台提交请求的时候,如果服务端响应事件比较长,就会导致需要等很长时间在前台才能接受到服务端返回的 响应结果,往往会导致用户重复点击按 ...