pthread_cond的实现使用了几个futex来协同进行同步,以及如何来实现的。

假定你已经明白 futex,futex-requeue,以及 pthread lowlevellock。

linux 内核的futex

linux 内核的futex - requeue 以及 requeue-pi

pthread的lowlevellock

pthread_cond一共使用了4个futex,其中包括1个外部的futex,是它所从属的Mutex。

pthread_cond自身包含的3个futex:

__lock:是一个lowlevellock用于pthread_cond原语操作在用户空间的临界区保护(或同步),同时保护pthread_cond的其它变量。

__futex:这是我们所熟知的条件变量阻塞队列。

__nwaiters:用作变量监视器,pthread_cond在destroy使用来同步所有没结束的wait。

这里可以看到futex除了锁之外另一种用法,用户空间可以阻塞在__nwaiters变量上,以Observer角色等待修改的一方发出更新的信号。pthread_cond_destroy会一直阻塞/唤醒监视__nwaiters,直到__nwaiters等于0为止。

pthread_cond_wait就是先后阻塞在__futex和__mutex.__lock两个futex队列进行等待,也就是futex_requeue系统调用中的futex1与futex2。pthread_cond_broadcast使用futex_requeue将__futex上的阻塞线程requeue到__mutex.__lock的等待队列上。

__total_seq 是 cond_wait调用的次序号。
__nwaiters 是 阻塞中的cond_wait的计数。
__wakeup_seq 是 发起唤醒的次序号,包括cond_signal 以及 cond_wait的timeout。
__broadcast_seq 是 cond_broadcast调用的次序号。
__woken_seq 是 cond_wait从阻塞中被唤醒的次序号。
__futex 是发起wakeup或wait的动作次序号。

规则:

__futex 在 __total_seq 和 __total_seq * 2 之间推进。__futex 为 __total_seq 和 __wakeup_seq 之和。

__total_seq 先于 __wakeup_seq 向前推进。每次wait调用使__total_seq向前推进1。后随的wakeup才能使__wakeup_seq向前推进。

__wakeup_seq 先于 __woken_seq 向前推进。wait被唤醒后将__woken_seq向前推进1,结束一次wait。

__broadcast_seq,独立的调用次序。每次broadcast,都会将__wakeup_seq,__wokenup_seq,以及__futex平衡,根据 __total_seq。

__nwaiters 未结束的 wait 调用,destroy 必须监视这个值,同步到这个值等于0才能安全进行销毁。

只有当__total_seq 不等于 __wakeup_seq 时,才能进行 signal 或 broadcast 。

当wait发现__futex这个参考次序号有变动,回到用户空间去调查情况:

1. 发现__wakeup_seq次序号有变动,即表明此时有signal调用,就可以与signal所唤醒的waiter进行竞争,竞争同步在用户空间临界区。如果__wakeup_seq != __woken_seq表明自己可以获得这次通行,如果__wakeup_seq == __woken_seq表示这次通行已经被某个waiter拔得头筹,只能决定再进内核排队。
2. 发现__broadcast_seq次序号有变动,即表明自身正包含在broadcast当中(自己修改的__total_seq,被broadcast采纳),可以不再进入内核排队,而直接获得通行。

wait都是三心两意的,一发现__futex有推进,就先打消排队的意愿,怀着被broadcast选中或幸运抢占到一次signal带来的机会,转头回到用户空间张望一下,没戏才失望地再次进入内核排队,但在排队前还是不死心。

下面来看pthread_cond_wait,pthread_cond_signal,以及pthread_cond_broadcast是如何在用户空间临界区同步的:

红色框:__lock保护的用户空间临界区。

紫色框:futex系统调用。

这里可以看到signal是全过程排他(mutual exclusion)进行的。而wait和broadcast则是用户空间和内核(系统调用)分开进行临界区同步的,它们在用户空间的代码只在用户空间的临界区同步,系统调用可以并发进行,当然也不是完全并发,在内核中系统调用的执行还是会同步进行的。换个说法,系统调用并不阻塞其它线程在用户空间在代码,一些线程阻塞在用户空间的临界区,不影响另一些线程去进行系统调用。

broadcast在唤醒线程时,是不会阻塞其它线程去调用wait的。但是signal 即使进入内核去唤醒线程时,也得阻塞其它线程去调用condvar的函数。

broadcast 与 broadcast 并发:

broadcast与broadcast同时调用,同步在用户空间临界区次序后面的broadcast,因为__total_seq == __wakeup_seq而退出,只有同步在用户临界区的第一个broadcast会继续单独执行。

signal 与 wait 和 broadcast 并发:

当signal调用时,可能有前面的wait或broadcast刚进入内核,但在signal结束之前,不允许任何用户空间的调用进入临界区。这情况下,前面的wait必须回到用户空间重新进入临界区,在signal调用完全结束之后,才能再次进入内核。

a. signal调用在broadcast执行期间,signal会因为__total_seq == __wakeup_seq而退出。
b. broadcast调用在signal执行期间,只能阻塞在用户空间临界区,等待signal调用结束。
c. signal调用时,前面已经有wait在执行,signal会将未能在内核临界区排队的wait全部延后,原理是,wait在内核临界区发现futex有变,即表明用户空间层有变动,必须先回到用户空间,延后次序再次进入内核。
d. wait调用在signal执行期间,wait只能阻塞在用户空间临界区,等待signal调用结束。

broadcast 与 wait 并发:

broadcast与wait同时调用,wait都会回到用户空间,检查自身是否包含在本次broadcast中,同步在用户空间临界区,先于broadcast的wait被包含在broadcast,后面的wait则排除在外。包含在本次broadcast中的wait则可以不用在内核排队而直接获得通行。

其它同步或锁的还有

linux 内核的spinlock如何实现排队》

linux 内核的另一个自旋锁 - 读写锁

linux 内核的各种futex功能》

linux 内核的rt_mutex (realtime互斥体)

linux 内核的futex pi-support,即pi-futex使用rt_mutex委托

linux 内核的rt_mutex 锁操作实现的临界区

phtread_mutex 组合

ACE框架 同步原语设计

futex-based pthread_cond 源代码分析的更多相关文章

  1. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

  2. 服务器程序源代码分析之三:gunicorn

    服务器程序源代码分析之三:gunicorn 时间:2014-05-09 11:33:54 类别:网站架构 访问: 641 次 gunicorn是一个python web 服务部署工具,类似flup,完 ...

  3. LIRe 源代码分析 7:算法类[以颜色布局为例]

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  4. LIRe 源代码分析 6:检索(ImageSearcher)[以颜色布局为例]

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  5. LIRe 源代码分析 5:提取特征向量[以颜色布局为例]

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  6. LIRe 源代码分析 4:建立索引(DocumentBuilder)[以颜色布局为例]

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  7. LIRe 源代码分析 3:基本接口(ImageSearcher)

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  8. LIRe 源代码分析 2:基本接口(DocumentBuilder)

    ===================================================== LIRe源代码分析系列文章列表: LIRe 源代码分析 1:整体结构 LIRe 源代码分析 ...

  9. LAV Filter 源代码分析 4: LAV Video (2)

    上一篇文章分析了LAV Filter 中的LAV Video的两个主要的类:CLAVVideo和CDecodeThread.文章:LAV Filter 源代码分析 3: LAV Video (1) 在 ...

随机推荐

  1. 初识mpvue

    听说mpvue可以实现H5和小程序的同时开发  对使用过vue的选手几乎是0难度 忍不住搓搓小手手 看了文  唔~ 似乎不是很难的样子 然后实际上手操作了一下 老规矩:新建项目 npm install ...

  2. 开源造轮子:一个简洁,高效,轻量级,酷炫的不要不要的canvas粒子运动插件库

    一:开篇 哈哈哈,感谢标题党的莅临~ 虽然标题有点夸张的感觉,但实际上,插件库确实是简洁,高效,轻量级,酷炫酷炫的咯.废话不多说,先来看个标配例子吧: (codepen在线演示编辑:http://co ...

  3. 玩转 RTC时钟库 DS3231

    1.前言     接着博主的上一篇 玩转 RTC时钟库 + DS1302,这一篇我们重点讲解DS3231时钟模块.没有看过上一篇的同学,麻烦先去阅读一下,因为很多理论基础已经在上一篇做了详细讲解,这里 ...

  4. 计算机网络(1)- TCP

    TCP的全称是传输控制协议(Transmission Control Protocol)[RFC 793] TCP提供面向连接的服务.在传送数据之前必须先建立连接,数据传送结束后要释放连接.TCP不提 ...

  5. NetworkManager网络通讯_问题汇总(四)

    此篇来填坑,有些坑是unet自身问题,而大部分则是理解不准确造成的(或者unity定义太复杂) 问题一: isLocalPlayer 值一直是false 出现场景:NetworkLobbyPlayer ...

  6. git的使用和常用命令

    git介绍 git 是一个免费开源的分布式版本控制系统 git可以实现各个版本之间的来回穿梭 git可以远程托管代码 git可以完成团队合作 workspace --add--> index - ...

  7. 透明度设置opacity

    透明度设置opacity属性 示例 <!DOCTYPE html> <html> <head> <style> div { background-col ...

  8. git从远程仓库拉取内容或向远程仓库上传内容

    一.将本地文件上传到远程仓库步骤 git init git add . git commit -m "初始框架" git remote add origin https://git ...

  9. Jdk14都要出了,还不能使用 Optional优雅的处理空指针?

    1. 前言 如果你没有处理过空指针,那么你不是一位真正的 Java 程序员. 空指针确实会产生很多问题,我们经常遇到空的引用,然后又想从这个空的引用上去获取其他的值,接着理所当然的碰到了 NullPo ...

  10. Java开发中的23中设计模式详解(一)工厂方法模式和抽象工厂模式

    一.设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接 ...