用条件变量实现事件等待器的正确与错误做法

TL;DR 如果你能一眼看出 https://gist.github.com/chenshuo/6430925 中的那 8 个 Waiter classes 哪些是对的哪些是错的,本文就不必看了。

前几天,我发了一条微博 http://weibo.com/1701018393/A7FrW7ZVd ,质疑某本书对 Pthreads 条件变量的封装是错的,因为它没有把 mutex 的 lock()/unlock() 函数暴露出来,导致无法实用。后来大家讨论的分歧是这个 cond class 是不是通用的条件变量封装,还是只是一个特殊的“事件等待器”。作为事件等待器,其实现也是错的,因为存在丢失事件的可能,可以算是初学者使用条件变量的典型错误。

本文的代码位于 recipes/thread/test/Waiter_test.cc,这里提到的某书的版本相当于 Waiter1 class。

我在拙作《Linux 多线程服务端编程:使用 muduo C++ 网络库》第 2.2 节总结了条件变量的使用要点:

条件变量只有一种正确使用的方式,几乎不可能用错。对于 wait 端:
1. 必须与 mutex 一起使用,该布尔表达式的读写需受此 mutex 保护。
2. 在 mutex 已上锁的时候才能调用 wait()。
3. 把判断布尔条件和 wait() 放到 while 循环中。

对于 signal/broadcast 端:
1. 不一定要在 mutex 已上锁的情况下调用 signal (理论上)。
2. 在 signal 之前一般要修改布尔表达式。
3. 修改布尔表达式通常要用 mutex 保护(至少用作 full memory barrier)。
4. 注意区分 signal 与 broadcast:“broadcast 通常用于表明状态变化,signal 通常用于表示资源可用。(broadcast should generally be used to indicate state change rather than resource availability。)”

如果用条件变量来实现一个“事件等待器/Waiter”,正确的做法是怎样的?我的最终答案见 WaiterInMuduo class。“事件等待器”的一种用途是程序启动时等待初始化完成,也可以直接用 muduo::CountDownLatch 到达相同的目的,将初值设为 1 即可。

以下根据微博上的讨论过程给出几个正确或错误的版本,博大家一笑。只要记住 Pthread 的条件变量是边沿触发(edge trigger),即 signal()/broadcast() 只会唤醒已经等在 wait() 上的线程(s),我们在编码时必须要考虑 signal() 早于 wait() 的可能,那么就很容易判断以下各个版本的正误了。代码见 recipes/thread/test/Waiter_test.cc

版本一:错误。某书上的原始版,有丢失事件的可能。

版本二:错误。lock() 之后再 signal(),同样有丢失事件的可能。

版本三:错误。引入了 bool signaled_; 条件,但没有正确处理 spurious wakeup。

版本四五六:正确。仅限 single waiter 使用。

版本七:最佳。可供 multiple waiters 使用。

版本八:错误。存在 data race,且有丢失事件的可能。理由见 http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex

总结:使用条件变量,调用 signal() 的时候无法知道是否已经有线程等待在 wait() 上。因此一般总是要先修改“条件”,使其为 true,再调用 signal();这样 wait 线程先检查“条件”,只有当条件不成立时才去 wait(),避免了丢事件的可能。换言之,通过使用“条件”,将边沿触发(edge trigger)改为电平触发(level trigger)。这里“修改条件”和“检查条件”都必须在 mutex 保护下进行,而且这个 mutex 必须用于配合 wait()。

思考题:如果用两个 mutex,一个用于保护“条件”,另一个专门用于和 cond 配合 wait(),会出现什么情况?

最后注明一点,http://stackoverflow.com/questions/6419117/signal-and-unlock-order 这篇帖子里对 spurious wakeup 的解释是错的,spurious wakeup 指的是一次 signal() 调用唤醒两个或以上 wait()ing 的线程,或者没有调用 signal() 却有线程从 wait() 返回。manpage 里对 Pthreads 系列函数的介绍非常到位,值得细读。

用条件变量实现事件等待器的正确与错误做法--转自陈硕的Blog的更多相关文章

  1. Linux环境编程之同步(二):条件变量

    相互排斥锁用于上锁,条件变量则用于等待.条件变量是类型为pthread_cond_t的变量.一般使用例如以下函数: #include <pthread.h> int pthread_con ...

  2. UNIX环境高级编程——线程同步之条件变量以及属性

    条件变量变量也是出自POSIX线程标准,另一种线程同步机制.主要用来等待某个条件的发生.可以用来同步同一进程中的各个线程.当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来 ...

  3. 四十一、Linux 线程——线程同步之条件变量

    41.1 概念 41.1.1 条件变量的介绍 互斥锁的缺点是它只有两种状态:锁定和非锁定 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足 条件变量内部是一个等待队列,放置等待 ...

  4. c++ 条件变量

    .条件变量创建 静态创建:pthread_cond_t cond=PTHREAD_COND_INITIALIZER; 动态创建:pthread_cond _t cond; pthread_cond_i ...

  5. linux C++ 多线程使用pthread_cond 条件变量

    1. 背景 多线程中经常需要使用到锁(pthread_mutex_t)来完成多个线程之间的互斥操作. 但是互斥锁有一个明显到缺点: 只有两种状态,锁定和非锁定. 而条件变量则通过允许线程阻塞并等待另一 ...

  6. pthread中互斥量,锁和条件变量

    互斥量 #include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INTIIALIZER; int pthread_mutex_in ...

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

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

  8. linux中的条件变量

    1 大家可能知道互斥量是线程程序中必须的工具了,但是也不能是万能的,就比如某个线程正在等待共享数据某个条件的发生,这个时候会发生什么呢.它就可能重复的尝试对互斥对象锁定和解锁来检查共享数据结构. 2 ...

  9. 第四十章 POSIX条件变量

    条件变量 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中.这种情况就需要用到条件 ...

随机推荐

  1. Spring Cloud面试题

    引言 面试中面试官喜欢问组件的实现原理,尤其是常用技术,我们平时使用了SpringCloud还需要了解它的实现原理,这样不仅起到举一反三的作用,还能帮助轻松应对各种问题及有针对的进行扩展.以下是 课程 ...

  2. 一、python简单爬取静态网页

    一.简单爬虫框架 简单爬虫框架由四个部分组成:URL管理器.网页下载器.网页解析器.调度器,还有应用这一部分,应用主要是NLP配合相关业务. 它的基本逻辑是这样的:给定一个要访问的URL,获取这个ht ...

  3. javaweb之MVC设计模式

    1.MVC简介 MVC是Model-View-Controller的简称,即模型-视图-控制器.MVC是一种设计模式,它把应用程序分成三个核心模块:模型,视图,控制器,它们各自处理自己的任务. 模型( ...

  4. mybatis笔记<二> 整合spring

    mybatis与spring整合需要添加几个jar包,mybatis-spring, spring-context, spring-jdbc 1. spring ioc只要一个jar包就ok 2. 我 ...

  5. java下double相乘精度丢失问题

    比如 System.out.println(0.14*100); 输出: 14.000000000000002 解决方法: BigDecimal b = new BigDecimal(String.v ...

  6. PHPStorm-Mintfy-And-Color-Schema

    美化Php-storm 1.隐藏一些工具条 打开一个项目后我习惯把一些工具条隐藏,在view菜单中把Tool buttons,Status bar,Navigation bar. CTRL+E 切换当 ...

  7. Python-模块3-re

    今天我们就说一个模块,那就是re,不过想要了解re模块,我们得先了解一下什么是正则表达式,有助于我们更好的学习re模块 一.正则表达式 首先, 我们在网页上进行注册或者登陆的时候经常能看到一些格式上的 ...

  8. Angular-ui/bootstarp modal 主控制器与模态框控制器传值

    调用模态框: $scope.open = function (size) { //这里很关键,是打开模态框的过程 var modalInstance = $uibModal.open({ animat ...

  9. Android学习——BroadCast(二)

    这篇博文介绍有序广播的使用 有序广播的发送 和普通广播完全相同,只需要更换发送的方式即可 ordered.setOnClickListener(new View.OnClickListener() { ...

  10. html 表单button

    做一下标记: 请务必为form里面button设置type 因为: Internet Explorer 的默认类型是 "button",而其他浏览器中(包括 W3C 规范)的默认值 ...