Linux 多线程条件变量同步
条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作。条件变量使用的条件本身是需要使用互斥量进行保护的,线程在改变条件状态之前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定之后才能计算条件。
模型
#include<pthread.h>
pthread_cond_t cond //准备条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //初始化静态的条件变量
pthread_cond_init() //初始化一个动态的条件变量
pthread_cond_wait() //等待条件变量变为真
pthread_cond_timedwait() //等待条件变量变为真,等待有时间限制。
pthread_cond_signal() //至少唤醒一个等待该条件的线程
pthread_cond_broadcast() //唤醒等待该条件的所有线程
pthread_cond_destroy() //销毁一个条件变量
pthread_cond_init()
//初始化一个动态的条件变量
//成功返回0,失败返回error number
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
cond:条件变量指针,这里使用了restrict关键字
attr:条件变量属性指针,默认属性赋NULL
pthread_cond_wait() / pthread_cond_timedwait()
//等待条件变量为真。收到pthread_cond_broadcast()或pthread_cond_signal()就唤醒
//成功返回0,失败返回error number
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
条件变量的使在多线程的程序中,因为各个线程都可以访问大部分的进程资源,所以我们为了保证公有资源的使用是可以控制的,在一个线程开始使用公有资源之前要尝试获取互斥锁,在使用完毕之后要释放互斥锁,这样就能保证每个公共资源在一个时刻都只能被一个线程使用,在一定程度上得到了控制,但这并不能解决同步的问题,考虑如下两个线程:
Q=create_queue();
pthread_t mutex
//线程A,入队
while(1){
lock(mutex);
in_queue(Q);
unlock(mutex);
}
//线程B,出队
while(1){
lock(mutex);
out_queue(Q);
unlock(mutex);
}
上述代码可以实现两个线程的互斥,即同一时刻只有一个线程在使用公有资源-队列。但如果线程B获取了锁,但队列中是空的,它的out_queue()也就是没有意义的,所以我们这里更需要一种方法将两个线程进行同步:只有当队列中有数据的时候才进行出队。
我们设计这样一种逻辑:
//线程B,出队
while(1){
lock(mutex);
if(队列是空,线程不应该执行){
释放锁;
continue;
}
out_queue(Q);
unlock(mutex);
}
这个程序就解决了上述的问题,即便线程B抢到了互斥锁,但是如果队列是空的,他就释放锁让两个线程重新抢锁,希望这次线程A能抢到并往里放一个数据。
但这个逻辑还有一个问题,就是多线程并发的问题,很有可能发生的一种情况是:线程B抢到了锁,发现没有数据,释放锁->线程A立即抢到了锁并往里放了一个数据->线程B执行continue,显然,这种情况下是不应该continue的,因为线程B想要的条件在释放锁之后立即就被满足了,它错过了条件。
So,我们想一种反过来的逻辑:
//线程B,出队
while(1){
lock(mutex);
if(队列是空,线程不应该执行){
continue;
释放锁;
}
out_queue(Q);
unlock(mutex);
}
显然这种方法有个致命的问题:一旦continue了,线程B自己获得锁就没有被释放,这样线程A不可能抢到锁,而B继续加锁就会形成死锁!
Finaly,我们希望看到一个函数fcn,如果条件不满足,能同时释放锁+停止执行线程,如果条件满足,自己当时获得的锁还在
//线程B,出队
while(1){
lock(mutex);
fcn(当前线程不应该执行,mutex) //if(当前线程不应该执行){释放锁“同时” continue;}
out_queue(Q);
unlock(mutex);
}
OK,这个就是pthread_cond_wait()的原理了,只不过它把continue变成了"休眠"这种由OS负责的操作,可以大大的节约资源。
当然,线程执行的条件是很难当作参数传入一个函数的,POSIX多线程的模型使用系统提供的"条件变量"+"我们自己定义的具体条件" 来确定一个线程是否应该执行接下来的内容。"条件变量"只有真和假,所以一种典型的多线程同步的结构如下
//线程B,出队
while(1){
lock(mutex);
while(条件不满足)
pthread_cond_wait(cond,mutex)
//获得互斥锁可以同时保护while里的条件和cond的判断,二者总是用一把锁保护,并一同释放
//cond为假,就休眠同时释放锁,等待被cond为真唤醒,把自己获得的锁拿回来
//拿回自己的锁再检查线程执行条件,条件不满足继续循环,直到条件满足跳出循环
//这个函数是带着"线程的执行条件为真"+"cond为真"走出循环的
//这个函数返回后cond被重新设置为0
out_queue(Q);
unlock(mutex);
}
pthread_cond_braoadcast()/pthread_cond_signal()
//使条件变量为真并唤醒wait中的线程,前者唤醒所有wait的,后者唤醒一个
//成功返回0,失败返回error number
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_destroy()
//销毁条件变量
//成功返回0,失败返回error number
int pthread_cond_destroy(pthread_cond_t *cond);
例子-线程池
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//thread.h
#ifndef __THREAD_H__
#define __THREAD_H__
#define THREAD_NUM 3
#define TASK_NUM 100
typedef struct{//每个节点的封装格式,fcn是用户自定义函数,arg是用户自定义函数参数指针,保证通用性声明为void
void* (*fcn)(void* arg);
void* arg;
}task_t;
typedef struct{ //用户自定义函数的参数结构体
int x;
}argfcn_t;
//#define LQ_DATA_T task_t*
#define LQ_DATA_T task_t
#endif //__THREAD_H__
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//lqueue.c
#include"thread.h"
#include"lqueue.h"
#include<stdlib.h>
...
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//thread_pool.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include"thread.h"
#include"lqueue.h"
//互斥量和条件变量
pthread_mutex_t lock;
pthread_cond_t cond;
//全局的表
lqueue_t* Q;
//每个线程的任务,必须是这个形式的函数指针
void* do_task(void* p){
task_t data;
int ret=0;
while(1){
pthread_mutex_lock(&lock);
while(is_empty_lqueue(Q)){ //大家收到广播,因为延迟,可能醒了好几个,要判断一下是不是自己
pthread_cond_wait(&cond,&lock); //先抢到锁再醒
}
ret=out_lqueue(Q,&data);
pthread_mutex_unlock(&lock);
data.fcn(data.arg);
}
}
//创建线程池
void create_pool(void){
//初始化队列
Q=create_lqueue();
//初始化互斥量
pthread_mutex_init(&lock,NULL);
//初始化条件变量
pthread_cond_init(&cond,NULL);
int i=THREAD_NUM;
pthread_t tid[THREAD_NUM];
while(i--)
pthread_create(&tid[i],NULL,do_task,NULL);
}
//准备函数
void* fcn(void* parg){ //用户自定义的需要线程执行的函数
argfcn_t* i=(argfcn_t*)parg;
printf("this is task1\n");
printf("task1:%d\n",i->x);
}
//添加任务
void pool_add_task(void*(*pfcn)(void*parg),void*arg){
task_t task;
task.fcn=pfcn;
task.arg=arg;
in_lqueue(Q,task);
pthread_cond_signal(&cond); //添加了一个任务,用signal更好
}
int main(int argc, const char *argv[])
{
//创建线程池
create_pool();
//准备参数
argfcn_t argfcn;
argfcn.x=5;
//添加任务
pool_add_task(fcn,(void*)&argfcn);
pool_add_task(fcn,(void*)&argfcn);
pool_add_task(fcn,(void*)&argfcn);
pause();
return 0;
}
Linux 多线程条件变量同步的更多相关文章
- 笔记3 linux 多线程 条件变量+互斥锁
//cond lock #include<stdio.h> #include<unistd.h> #include<pthread.h> struct test { ...
- Linux Qt使用POSIX多线程条件变量、互斥锁(量)
今天团建,但是文章也要写.酒要喝好,文要写美,方为我辈程序员的全才之路.嘎嘎 之前一直在看POSIX的多线程编程,上个周末结合自己的理解,写了一个基于Qt的用条件变量同步线程的例子.故此来和大家一起分 ...
- python多线程编程5: 条件变量同步-乾颐堂
互斥锁是最简单的线程同步机制,Python提供的Condition对象提供了对复杂线程同步问题的支持.Condition被称为条件变量,除了提供与Lock类似的acquire和release方法外,还 ...
- Python学习---线程锁/信号量/条件变量同步/线程池1221
线程锁 问题现象: 多线程情况下,CPU遇到阻塞会进行线程的切换,所以导致执行了tmp-=1的值还未赋值给num=tmp,另一个线程2又开始了tmp -=1,所以导致最后的值重复赋值给了num,所以出 ...
- 练习生产者与消费者-PYTHON多线程中的条件变量同步-Queue
以前练习过,但好久不用,手生,概念也生了, 重温一下.. URL: http://www.cnblogs.com/holbrook/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/ ~ ...
- 【2017-06-20】Linux应用开发工程师C/C++面试问题记录之一:Linux多线程程序的同步问题
参考之一:Linux 线程同步的三种方法 链接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 简要回答: Linux下线程同步最常用的三种方法就是互斥 ...
- C++11并发——多线程条件变量std::condition_variable(四)
https://www.jianshu.com/p/a31d4fb5594f https://blog.csdn.net/y396397735/article/details/81272752 htt ...
- Linux:条件变量
条件变量: 条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数: pthread_cond_init函数 pthrea ...
- Linux多线程--使用信号量同步线程【转】
本文转载自:http://blog.csdn.net/ljianhui/article/details/10813469 信号量.同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过 ...
随机推荐
- String和StringBuffer的区别
1.选自http://szh-java.iteye.com/blog/1666630 String 1,Stirng是对象不是基本数据类型 2,String是final类,不能被继承.是不可变对象,一 ...
- Scalaz(25)- Monad: Monad Transformer-叠加Monad效果
中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾 ...
- 利用Redis cache优化app查询速度实践
注意:本篇文章译自speeding up existing app with a redis cache,如需要转载请注明出处. 发现问题 在应用解决方法之前,我们需要对我们面对的问题有一个清晰的认识 ...
- 理解 OpenStack 高可用(HA) (6): MySQL HA
本系列会分析OpenStack 的高可用性(HA)概念和解决方案: (1)OpenStack 高可用方案概述 (2)Neutron L3 Agent HA - VRRP (虚拟路由冗余协议) (3)N ...
- phonegap安卓手机开发入门
先安装安卓开发安环境 http://www.cnblogs.com/zhangsanshi/p/3582368.html 安装phonegap 在安装ant http://www.cnblogs.co ...
- 从0开始学angularjs-笔记02
上一节课主要跟大家讲解了angularjs的几种特性和看了一个简单的双向绑定的例子.最近都没有时间写博客了....忙成狗呀...今天周末,在写一篇吧~~ 今天主要跟大家详细讲解一下angularjs的 ...
- kali 忘记登录密码后重置的方法
首先启动你的卡里系统,等出现引导界面时选择恢复模式.如下图: 再出来一个界面时,选择第二个并按E键进入编辑模式.如下图: 进入编辑模式后找到如下图的代码: 把ro改为rw,并且在.gz 后面写上ini ...
- 关于一些网络代理实现智能流量分流的研究(PAC脚本介绍及利用)
因为工作原因,需要访问一些国外的网站(科学上网),但直接FQ并不方便,于是研究了一些代理软件,比如Nydus,Green等, 在Nydus的Proxy版本中发现了实现国内国外流量的智能分流的办法,通过 ...
- Android Design Principles
Android Design Principles Enchant Me Delight me in surprising ways 用惊奇的方式取悦用户 漂亮的界面,仔细放置的动画,一个恰到时机的音 ...
- 复杂sql分组查询 ( pivot)
一个数据表里面字段有年.月.日.金额.支付方式等字段,然后现在想写个sql语句,把每一天的每种支付方式金额(支付方式有多重)排在同一行, 最后在增加一列小计当前的所有支付方式的金额.如下图: 原sql ...