条件变量(Condition Variables)

参考资料:http://game-lab.org/posts/posix-thread-cn/#5.1

条件变量是什么?

  • 条件变量为我们提供了另一种线程间同步的方法,然而,互斥量是通过控制线程访问数据来实现同步,条件变量允许线程同步是基于实际数据的值。
  • 如果没有条件变量,程序员需要让线程不断地轮询,以检查是否满足条件。由于线程处在一个不间断的忙碌状态,所以这是相当耗资源的。条件变量就是这么一个不需要轮询就可以解决这个问题的方法。
  • 条件变量总是跟互斥锁(mutex lock)一起使用。
  • 下面是使用条件变量的比较典型的过程:
主线程

  • 声明并初始化需要同步的全局数据或变量(例如”count“)
  • 声明并初始化一个条件变量对象
  • 声明并初始化一个与条件变量关联的互斥量
  • 创建线程A和B并开始运行
线程A

  • 线程运转至某一个条件被触发(例如,”count“必须达到某个值)
  • 锁定相关联的互斥量并检查全局变量的值
  • 调用pthread_con_wait()阻塞线程等待线程B的信号。请注意,调用pthread_con_wait()以自动的原子方式(atomically)解锁相关联的互斥量,以便于可以被线程B使用。
  • 当收到信号时,唤醒线程。互斥量被以自动的原子方式被锁定。
  • 明确的解锁互斥量。
  • 继续
Thread B

  • 线程运转
  • 锁定相关联的互斥量
  • 更改线程A正在等待的全局变量的值
  • 检查线程A等待的变量值,如果满足条件,发信号给线程A
  • 解锁互斥量
  • 继续
主线程

    Join / Continue

创建和销毁条件变量

函数:

pthread_cond_init (condition,attr)
pthread_cond_destroy (condition) pthread_condattr_init (attr)
pthread_condattr_destroy (attr)

用法:

  • 条件变量必须声明为pthread_cond_t类型,并且在使用前必须要初始化。初始化,有两种方法:
  1. 静态初始化,像这样声明:pthread_con_t myconvar = PTHREAD_CON_INITIALIZER;
  2. 动态初始化,使用pthread_cond_init()函数。用创建条件变量的ID作为件参数传给线程,这种方法允许设置条件变量对象属性attr。
  • 可设置的attr对象经常用来设置条件变量的属性,条件变量只有一种属性:process-thread,它的作用是允许条件变量被其它进程的线程看到。如果使用属性对象,必须是pthread_condattr_t类型(也可以赋值为NULL,作为默认值)。

注意,不是所有的实现都用得着process-shared属性。

  • pthread_condattr_init()和pthread_condattr_destroy()函数是用来创建和销毁条件变量属性对象的。
  • 当不再需要某条件变量时,可用pthread_cond_destroy()销毁。

条件变量的等待和信号发送

函数:

pthread_cond_wait (condition,mutex)
pthread_cond_signal (condition)
pthread_cond_broadcast (condition)

使用:

  • pthread_cond_wait()阻塞调用线程,直到指定的条件变量收到信号。当互斥量被锁定时,应该调用这个函数,并且在等待时自动释放这个互斥量,在接收到信号后线程被唤醒,线程的互斥量会被自动锁定,程序员在线程中应当在此函数后解锁互斥量。
  • pthread_cond_signal()函数常用来发信号给(或唤醒)正在等待条件变量的另一个线程,在互斥量被锁定后应该调用这个函数,并且为了pthread_cond_wait()函数的完成必须要解锁互斥量。
  • 如果多个线程处于阻塞等待状态,那么必须要使用pthreads_cond_broadcast()函数,而不是pthread_cond_signal()。
  • 在调用pthread_cond_wait()函数之前调用pthread_cond_signal()函数是个逻辑上的错误,虽然如此,但是pthread_cond_signal()函数仍然能够正常返回。所以,在使用这些函数时,正确的锁定和解锁与条件变量相关的互斥量是非常必要的,例如:
  1. 在调用pthread_cond_wait()之前锁定互斥量失败,可致使其无法阻塞;
  2. 在调用pthread_cond_signal()之后解锁互斥量失败,则致使与之对应的pthread_cond_wait()函数无法完成,并仍保持阻塞状态。

注意事项:关于pthread_cond_signal和pthread_cond_wait函数存放位置问题:

  pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间,因为他要根据共享变量的状态来觉得是否要等待,而为了不永远等待下去所以必须要在lock/unlock队中共享变量的状态改变必须遵守lock/unlock的规则。
  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的线程),而这在上面的放中间的模式下是不会出现的。 所以,在Linux下最好pthread_cond_signal放中间,但从编程规则上说,其他两种都可以

实例分析

 /******************************************************************************
* 描述:
* 应用Pthreads条件变量的实例代码,主线程创建三个线程,其中两个为“count”变量做
* 加法运算,第三个线程监视“count”的值。当“count”达到一个限定值,等待线程准备接收来
* 自于两个加法线程中一个的信号,等待 线程唤醒后更改“count”的值。程序继续运行直到加法
* 线程达到TCOUNT的值。最后,主程序打印出count的值。
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h> #define NUM_THREADS 3 //线程个数
#define TCOUNT 5 //单线程轮询次数
#define COUNT_LIMIT 7 //发送信号的次数
int count = ; //全局的累加量 pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv; void *inc_count(void *t)
{
long my_id = (long) t; for (int i = ; i < TCOUNT; i++)
{
pthread_mutex_lock(&count_mutex);
count++;
/*
* 检查count的值,如果条件满足就发信号给等待线程
* 注意,此处是用信号量锁定的。
* */
if (count < COUNT_LIMIT)
{
printf("inc_count(): thread %ld, count = %d Threshold reached. ",
my_id, count);
pthread_cond_signal(&count_threshold_cv);
/* pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.
* 如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
* */
printf("Just sent signal.\n");
}
printf("inc_count(): thread %ld, count = %d, unlocking mutex\n", my_id,
count);
pthread_mutex_unlock(&count_mutex); /*为线程轮询互斥锁增加延时*/
Sleep();
}
pthread_exit(NULL);
return ;
} void *watch_count(void *t)
{
long my_id = (long) t;
printf("Starting watch_count(): thread %ld\n", my_id); /************************************************************************/
/* 为什么要与pthread_mutex 一起使用呢? */
/* 这是为了应对线程1在调用pthread_cond_wait()但线程1还没有进入wait cond */
/* 的状态的时候,此时线程2调用了cond_singal的情况。如果不用mutex锁的话, */
/* 这个cond_singal就丢失了。加了锁的情况是,线程2必须等到 mutex 被释放 */
/*(也就是 pthread_cod_wait() 进入wait_cond状态 并自动释放mutex)的时 */
/* 候才能调用cond_singal(前提:线程2也使用mutex)。 */
/************************************************************************/ /* 锁定互斥量并等待信号,注意,pthread_cond_wait函数在等待时将自动以自动原子方式
* 解锁互斥量。还有,请注意,如果等待线程运行到等待函数之前已经满足COUNT_LIMIT的
* 条件判断,轮询会忽略掉等待函数,
* */
while (count < COUNT_LIMIT)
{
pthread_mutex_lock(&count_mutex);
printf("watch_count(): thread %ld going into wait...\n", my_id);
pthread_cond_wait(&count_threshold_cv, &count_mutex);
/* pthread_cond_wait函数一进入wait状态就会自动release mutex,一旦wait成功获得
* cond条件变量后会自动lock mutex
* */
printf("watch_count(): thread %ld Condition signal received.\n", my_id); printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
pthread_mutex_unlock(&count_mutex);
}
pthread_exit(NULL);
return ;
} int main(int argc, char *argv[])
{
int i;
long t1 = , t2 = , t3 = ;
pthread_t threads[];
pthread_attr_t attr; /*初始化互斥量和条件变量对象*/
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init(&count_threshold_cv, NULL); /*创建线程时设为可连接状态,便于移植*/
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); // 可以省略,线程默认属性也是joinable
pthread_create(&threads[], &attr, watch_count, (void *) t1);
pthread_create(&threads[], &attr, inc_count, (void *) t2);
pthread_create(&threads[], &attr, inc_count, (void *) t3); /* 等待所有线程完成*/
for (i = ; i < NUM_THREADS; i++)
{
pthread_join(threads[i], NULL);
}
/*发送信号给监听线程*/
pthread_cond_signal(&count_threshold_cv);
pthread_join(threads[],NULL);
printf("Main(): Waited on %d threads. Final value of count = %d. Done.\n",
NUM_THREADS, count); /*清除并退出 */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv); system("pause");
pthread_exit(NULL);
}

输出结果:

Starting watch_count(): thread
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
watch_count(): thread going into wait...
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
watch_count(): thread Condition signal received.
watch_count(): thread count now = .
watch_count(): thread going into wait...
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
watch_count(): thread Condition signal received.
watch_count(): thread count now = .
watch_count(): thread going into wait...
inc_count(): thread , count = Threshold reached. Just sent signal.
inc_count(): thread , count = , unlocking mutex
watch_count(): thread Condition signal received.
watch_count(): thread count now = .
watch_count(): thread going into wait...
inc_count(): thread , count = , unlocking mutex
inc_count(): thread , count = , unlocking mutex
inc_count(): thread , count = , unlocking mutex
inc_count(): thread , count = , unlocking mutex
watch_count(): thread Condition signal received.
watch_count(): thread count now = .
Main(): Waited on threads. Final value of count = . Done.
请按任意键继续. . .

 

pThreads线程(三) 线程同步--条件变量的更多相关文章

  1. (转)Java线程:新特征-条件变量

    Java线程:新特征-条件变量   条件变量是Java5线程中很重要的一个概念,顾名思义,条件变量就是表示条件的一种变量.但是必须说明,这里的条件是没有实际含义的,仅仅是个标记而已,并且条件的含义往往 ...

  2. C++11 中的线程、锁和条件变量

    转自:http://blog.jobbole.com/44409/ 线程 类std::thread代表一个可执行线程,使用时必须包含头文件<thread>.std::thread可以和普通 ...

  3. 【转】【C++】C++ 中的线程、锁和条件变量

    线程 类std::thread代表一个可执行线程,使用时必须包含头文件<thread>.std::thread可以和普通函数,匿名函数和仿函数(一个实现了operator()函数的类)一同 ...

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

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

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

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

  6. c++11中的线程、锁和条件变量

    void func(int i, double d, const string& s) { cout << i << ", " << d ...

  7. Pthreads 信号量,路障,条件变量

    ▶ 使用信号量来进行线程间信息传递 ● 代码 #include <stdio.h> #include <pthread.h> #include <semaphore.h& ...

  8. MFC线程(三):线程同步事件(event)与互斥(mutex)

    前面讲了临界区可以用来达到线程同步.而事件(event)与互斥(mutex)也同样可以做到. Win32 API中的线程事件 HANDLE hEvent = NULL; void MainTestFu ...

  9. [并发编程]使用线程安全队列和条件变量的notify来安排分步骤任务

    // 方法1:直接构建N个THread来run foreach (i, size) { thread trd(&Instance::doWork, &inst); lstTrd.pus ...

随机推荐

  1. NOI.AC NOIP模拟赛 第五场 游记

    NOI.AC NOIP模拟赛 第五场 游记 count 题目大意: 长度为\(n+1(n\le10^5)\)的序列\(A\),其中的每个数都是不大于\(n\)的正整数,且\(n\)以内每个正整数至少出 ...

  2. 最短路径:(Dijkstra & Floyd)

    Dijkstra算法 1.定义概览 Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径.主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止.Di ...

  3. BZOJ 1009 HNOI 2008 GT考试 递推+矩乘

    1009: [HNOI2008]GT考试 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 3679  Solved: 2254[Submit][Statu ...

  4. Codeforces Round #373 (Div. 2) B. Anatoly and Cockroaches 水题

    B. Anatoly and Cockroaches 题目连接: http://codeforces.com/contest/719/problem/B Description Anatoly liv ...

  5. Android 获取手机信息,设置权限,申请权限,查询联系人,获取手机定位信息

    Android 获取手机信息,设置权限,申请权限,查询联系人,获取手机定位信息 本文目录: 获取手机信息 设置权限 申请权限 查询联系人 获取手机定位信息 调用高德地图,设置显示2个坐标点的位置,以及 ...

  6. ios网络请求

    1.AFNetworking object 2.Alamofire swift

  7. [原创]浅谈H5页面测试介绍

    [原创]浅谈H5页面测试介绍 目前移动互联网非常火热,除了各种App,H5也是非常热,由于H5跨平台,且版本更新容易,做为引流或获客是非常好的一种简单低成本平台:今天来谈谈H5页面测试都要测试什么? ...

  8. asp.net core中的razor页面

    Razor 页面(Razor Pages)是 ASP.NET Core 2.0 中新增的一种Web页面模型,相对MVC形式更加简单易用,可以说是一个服务端的MVVM模型,本文简单的介绍一下它的用法. ...

  9. 【Android基础篇】TabWidget设置背景和字体

    在使用TabHost实现底部导航栏时,底部导航栏的三个导航button无法在布局文件中进行定制.比方设置点击时的颜色.字体的大小及颜色等,这里提供了一个解决的方法.就是在代码里进行定制. 思路是在Ac ...

  10. java 输入一个字符串,打印出该字符串中字符的所有排列

    import java.util.Scanner; public class Demo001 { public static void main(String[] args) { String str ...