再谈互斥锁与条件变量!(终于搞清楚了啊!!!!!)
1、pthread_cond_wait总和一个互斥锁结合使用。
(1)在调用pthread_cond_wait前要先获取锁。(为了防止多个线程同时请求pthread_cond_wait)
(2)pthread_cond_wait函数功能:先原子地 1)把调用线程放到等待条件的线程列表上 2)释放指定的锁以供其他线程(生产者)添加任务——>然后就是等待条件变量的变化(任务添加完毕)——>最后在函数调用返回(前)的时候又会自动地将指定的互斥量重新锁住。
2、int pthread_cond_signal(pthread_cond_t * cond);
pthread_cond_signal通过条件变量cond发送消息,若多个消息在等待,它只唤醒一个。pthread_cond_broadcast可以唤醒所有。调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。不过apue上的是释放锁后立马调用应该也ok。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求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()则激活所有等待线程。
下面是另一处说明:给出了函数运行全过程。 为什么在唤醒线程后要重新mutex加锁?
了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。
首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。那么这个特定线程什么也干不了——其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能:
锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。
pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。 【要求解锁并阻塞是一个原子操作】
此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。
现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。
现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。
来看一个例子(你是否能理解呢?):
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的线程),而这在上面的放中间的模式下是不会出现的。
关于APUE上的例子
具体的函数介绍就不说了,详细参考APUE,下面通过一个例子来详细说一下正确使用条件变量的方法。下例实现了生产者和消费者模型,生产者向队列中插入数据,消费者则在生产者发出队列准备好(有数据了)后接收消息,然后取出数据进行处理。实现的关键点在以下几个方面:
- 生产者和消费者都对条件变量的使用加了锁
- 消费者调用pthread_cond_wait,等待队列是否准备好的信息,注意参数有两个,一个是pthread_cond_t,另外一个是pthread_mutex_t.
代码:
#include <pthread.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void
process_msg(void)
{struct msg *mp;for (;;) {pthread_mutex_lock(&qlock);while (workq == NULL)pthread_cond_wait(&qready, &qlock);mp = workq;workq = mp->m_next;pthread_mutex_unlock(&qlock);/* now process the message mp */}
}
void
enqueue_msg(struct msg *mp)
{pthread_mutex_lock(&qlock);mp->m_next = workq;workq = mp;pthread_mutex_unlock(&qlock);pthread_cond_signal(&qready);
}
关于上面例子的几个疑问
为什么pthread_cond_wait前需要加锁??
pthread_cond_wait中的mutex用于保护条件变量,调用这个函数进行等待条件的发生时,mutex会被自动释放,以供其它线程(生产者)改变条件,pthread_cond_wait中的两个步骤必须是原子性的(atomically,万恶的APUE中文版把这个单词翻译成了『自动』,误人子弟啊),也就是说必须把两个步骤捆绑到一起:
- 把调用线程放到条件等待队列上
- 释放mutex
不然呢,如果不是原子性的,上面的两个步骤中间就可能插入其它操作。比如,如果先释放mutex,这时候生产者线程向队列中添加数据,然后signal,之后消费者线程才去『把调用线程放到等待队列上』,signal信号就这样被丢失了。
如果先把调用线程放到条件等待队列上,这时候另外一个线程发送了pthread_cond_signal(我们知道这个函数的调用是不需要mutex的),然后调用线程立即获取mutex,两次获取mutex会产生deadlock.
至于原因就是前面说的防止线程1还没有进入等待条件的线程列表的时候,线程1却调用pthread_cond_signal的情况。
在生产者线程中修改条件时为什么要加mutex??
如果不这么做信号可能会丢失,看下面的例子:
Thead A Thread Bpthread_mutex_lock(&qlock);
while (workq == NULL)mp->m_next = workq;workq = mp;pthread_cond_signal(&cond);pthread_cond_wait(&qready, &qlock);
在while判断之后向队列中插入数据,虽然已经有数据了,但线程A还是调用了pthread_cond_wait等待下一个信号到来。。
消费者线程中判断条件为什么要放在while中??
while (workq == NULL)pthread_cond_wait(&qready, &qlock);
mp = workq;
我们把while换成if可不可以呢?
if (workq == NULL)pthread_cond_wait(&qready, &qlock);
mp = workq;
答案是不可以,一个生产者可能对应着多个消费者,生产者向队列中插入一条数据之后发出signal,然后各个消费者线程的pthread_cond_wait获取mutex后返回,当然,这里只有一个线程获取到了mutex,然后进行处理,其它线程会pending在这里,处理线程处理完毕之后释放mutex,刚才等待的线程中有一个获取mutex,如果这里用if,就会在当前队列为空的状态下继续往下处理,这显然是不合理的。
signal到底是放在unlock之前还是之后??
void
enqueue_msg(struct msg *mp)
{pthread_mutex_lock(&qlock);mp->m_next = workq;workq = mp;pthread_mutex_unlock(&qlock);pthread_cond_signal(&qready);
}
如果先unlock,再signal,如果这时候有一个消费者线程恰好获取mutex,然后进入条件判断,这里就会判断成功,从而跳过pthread_cond_wait,下面的signal就会不起作用;另外一种情况,一个优先级更低的不需要条件判断的线程正好也需要这个mutex,这时候就会转去执行这个优先级低的线程,就违背了设计的初衷。
void
enqueue_msg(struct msg *mp)
{pthread_mutex_lock(&qlock);mp->m_next = workq;workq = mp;pthread_cond_signal(&qready);pthread_mutex_unlock(&qlock);
}
如果把signal放在unlock之前,消费者线程会被唤醒,获取mutex发现获取不到,就又去sleep了。浪费了资源.但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。
再谈互斥锁与条件变量!(终于搞清楚了啊!!!!!)相关推荐
- 信号量,互斥锁,条件变量的联系与区别
转自:http://blog.chinaunix.net/u3/108685/showart_2127853.html 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程, ...
- 【C++】多线程互斥锁、条件变量
我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量和条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!! 先来看下面这段简单的代码: int g_num = 0 ...
- 互斥锁、条件变量、自旋锁、读写锁
一.互斥锁 机制:一次只能一个线程拥有互斥锁,其他线程只有等待. 互斥锁是在抢锁失败的情况下主动放弃CPU,进入睡眠状态直到锁的状态改变时再唤醒,而操作系统负责线程调度,为了实现锁的状态发生改变时能唤 ...
- 非常精简的Linux线程池实现(一)——使用互斥锁和条件变量
https://blog.csdn.net/kxcfzyk/article/details/31719687 线程池的含义跟它的名字一样,就是一个由许多线程组成的池子. 有了线程池,在程序中使用多线程 ...
- linux线程间同步(1)互斥锁与条件变量
线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点.linux下提供了多种方式来处理线程同步,最常用的是互斥锁.条件变量和信号量以及读写锁. 互斥锁(mutex) 互斥锁,是一种信 ...
- 互斥锁、条件变量、信号量浅析
互斥锁.条件变量.信号量浅析 互斥锁与条件变量 条件变量是为了保证同步 条件变量用在多线程多任务同步的,一个线程完成了某一个动作就通过条件变量告诉别的线程,别的线程再进行某些动作(大家都在semtak ...
- 进程通信学习笔记(互斥锁和条件变量)
1.互斥锁:上锁和解锁 Posix互斥锁作为数据类型pthread_mutex_t的变量声明.如果互斥锁变量是静态分配的,那么可以把它初始化成常值PTHREAD_MUTEX_INITIALIZER.如 ...
- Linux系统编程:使用mutex互斥锁和条件变量实现多个生成者和消费者模型
实现代码 如题,使用mutex互斥锁和条件变量实现多个生成者和消费者模型. 直接上代码,需要线程中的互斥锁和条件变量的相关知识进行支撑.这里就不细说了呀,代码中有一定的注释. #include < ...
- Linux多线程编程---线程间同步(互斥锁、条件变量、信号量和读写锁)
本篇博文转自http://zhangxiaoya.github.io/2015/05/15/multi-thread-of-c-program-language-on-linux/ Linux下提供了 ...
- linux互斥锁和条件变量,如何理解互斥锁和条件变量?
下面的代码出自<Unix/Linux编程实践教程>,作用是用两个线程分别统计两个文件的单词的数目,并在主线程中计算总数.下面是运行截图: 但是看了半天还是难以理解下面代码中的加锁.解锁以及 ...
最新文章
- 第2篇 C#数据类型-值类型与引用类型
- 【NetApp】重删和压缩的关系
- PostgreSQL 务实应用(三/5)分表复制
- 开始学习Solaris
- js中的window.onload和jquery中的load区别的讲解
- pprof 的原理与实现
- jqprint获取打印页数_高年级应用题40道,假期快给孩子打印练习吧!(含答案)...
- “玲珑杯”线上赛 Round #15 河南专场 F		咸鱼文章
- 经典面试题(12):关于事件循环,以下代码将输出什么?
- React hooks + antd前台实现input搜索框实时搜索table表格
- SpringColoud学习笔记007---杂七杂八001--@Resource与@Autowired注解的区别
- python发展调研报告_实战 | Python自动生成PPT调研报告
- Java判断是否为素数
- JS生成验证码、卡密,生成指定位数的字符串
- html跳转页面携带数据
- 矩阵快速幂 算法原理
- 烧写器--SPI NAND FLASH烧录定制说明
- 硬盘安装XP蓝屏,U盘安装XP提示INF file txtsetup……
- 传奇人物____Anders Hejlsberg
- 始于情怀,终于品质----方寸微T630 USB3.0 替换 CYUSB3014