最近在温习pthread的时候,忽然发现以前对pthread_cond_wait的了解太肤浅了。昨晚在看《Programming With POSIX Threads》的时候,看到了pthread_cond_wait的通常使用方法:

pthread_mutex_lock();

while(condition_is_false)

    pthread_cond_wait();

pthread_mutex_unlock();

为什么在pthread_cond_wait()前要加一个while循环来判断条件是否为假呢?

APUE中写道:

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。

线程释放互斥量,等待其他线程发给该条件变量的信号(唤醒一个等待者)或广播该条件变量(唤醒所有等待者)。当等待条件变量时,互斥量必须始终为释放的,这样其他线程才有机会锁住互斥量,修改条件变量。当线程从条件变量等待中醒来时,它重新继续锁住互斥量,对临界资源进行处理。

条件变量的作用是发信号,而不是互斥。

wait前检查

对于多线程程序,不能够用常规串行的思路来思考它们,因为它们是完全异步的,会出现很多临界情况。比如:pthread_cond_signal的时间早于pthread_cond_wait的时间,这样pthread_cond_wait就会一直等下去,漏掉了之前的条件变化。

对于这种情况,解决的方法是在锁住互斥量之后和等待条件变量之前,检查条件变量是否已经发生变化。

if(condition_is_false)

    pthread_cond_wait();

这样在等待条件变量前检查一下条件变量的值,如果条件变量已经发生了变化,那么就没有必要进行等待了,可以直接进行处理。这种方法在并发系统中比较常见,例如之前PACKET_MMAP中poll的竞争条件的解决方法

-----------------------------------------------------------------------

忽然想起了设计模式中的单件模式的"双重检查加锁":

Singleton *getInstance()

{

    if(ptr==NULL)

    {

        LOCK();

        if(ptr==NULL)

        {

            ptr = new Singleton();

        }

        UNLOCK();

    }

    return ptr;

}

这样只有在第一次的时候会进行锁(应该是第一轮,如果刚开始有多个线程进入了最上层的ptr==NULL代码块,就会有多次锁,只不过之后就不会锁了),之后就不会锁了。

pthread_once()的实现也是基于单件模式的。

pthread_once函数首先检查控制变量,以判断是否已经完成初始化。如果完成,pthread_once简单的返回;否则,pthread_once调用初始化函数(没有参数),并记录下初始化被完成。如果在一个线程初始化时,另外的线程调用pthread_once,则调用线程将等待,直到那个线程完成初始化后返回。换句话,当调用pthread_once成功返回时,调用者能够肯定所有的状态已经初始化完毕。

int

__pthread_once (once_control, init_routine)

     pthread_once_t *once_control;

     void (*init_routine) (void);

{

  /* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a

     global lock variable or one which is part of the pthread_once_t

     object.  */

  if (*once_control == PTHREAD_ONCE_INIT)

    {

      lll_lock (once_lock, LLL_PRIVATE);

      /* XXX This implementation is not complete.  It doesn't take

cancelation and fork into account.  */

      if (*once_control == PTHREAD_ONCE_INIT)

{

  init_routine ();

  *once_control = !PTHREAD_ONCE_INIT;

}

      lll_unlock (once_lock, LLL_PRIVATE);

    }

  return ;

}

-----------------------------------------------------------------------

pthread_cond_wait中的while()不仅仅在等待条件变量前检查条件变量,实际上在等待条件变量后也检查条件变量。pthread_cond_wait返回后,还需要检查条件变量,这是为什么呢?难道pthread_cond_wait不是pthread_cond_signal触发了某个condition导致的吗?

这个地方有些迷惑人,实际上pthread_cond_wait的返回不仅仅是pthread_cond_signal和pthread_cond_broadcast导致的,还会有一些假唤醒,也就是spurious wakeup。

何为假唤醒?顾名思义就是虚假的唤醒,与pthread_cond_signal和pthread_cond_broadcast的唤醒相对。那么什么情况下会导致假唤醒呢?可以阅读参考1。

signal

大致意思是:

在linux中,pthread_cond_wait底层是futex系统调用。在linux中,任何慢速的阻塞的系统调用当接收到信号的时候,就会返回-1,并且设置errno为EINTR。在系统调用返回前,用户程序注册的信号处理函数会被调用处理。

 

注:什么有样的系统调用会出现接收信号后发挥EINTR呢?

慢速阻塞的系统调用,有可能会永远阻塞下去的那种。当接收到信号的时候,认为是一个返回并执行其他代码的一个时机。

信号的处理也不简单,因为有些慢系统调用被信号中断后是会自动重启的,所以我们通常需要用siginterrupt(signo, 1)来关闭重启或者在用sigaction安装信号处理函数的时候取消SA_RESTART标志,之后就可以通过判断信号的返回值是否是-1和errno是否为EINTR来判断是否有信号抵达。

如果关闭了SA_RESTART的一些使用慢速系统调用的应用,一般都采用while()循环,检测到EINTR后就重新调用。

while()

{

   int ret = syscall();

   if(ret< && errno==EINTR)

       continue;

   else

       break;

}

但是,对于futex这种方法不行,因为futex结束后,再重新运行的过程中,会出现一个时间窗口,其他线程可能会在这个时间窗口中进行pthread_cond_signal,这样,再进行pthread_cond_wait的时候就丢失了一次条件变量的变化。解决方法就是在pthread_cond_wait前检查条件变量,也就是

pthread_mutex_lock();

while(condition_is_false)

    pthread_cond_wait();

pthread_mutex_unlock();

pthread_cond_broadcast

实际上,不仅仅信号会导致假唤醒,pthread_cond_broadcast也会导致假唤醒。加入条件变量上有多个线程在等待,pthread_cond_broadcast会唤醒所有的等待线程,而pthread_cond_signal只会唤醒其中一个等待线程。这样,pthread_cond_broadcast的情况也许要在pthread_cond_wait前使用while循环来检查条件变量。

转至:http://www.cnblogs.com/leaven/archive/2010/06/03/1750973.html

pthread_cond_wait的spurious wakeup问题的更多相关文章

  1. 多线程编程中条件变量和的spurious wakeup 虚假唤醒

    1. 概述 条件变量(condition variable)是利用共享的变量进行线程之间同步的一种机制.典型的场景包括生产者-消费者模型,线程池实现等. 对条件变量的使用包括两个动作: 1) 线程等待 ...

  2. 什么是虚假唤醒 spurious wakeup

    解释一下什么是虚假唤醒? 说具体的例子,比较容易说通. pthread_mutex_t lock; pthread_cond_t notempty; pthread_cond_t notfull; v ...

  3. 刨根问底系列(1)——虚假唤醒(spurious wakeups)的原因以及在pthread_cond_wait、pthread_cond_singal中使用while的必要性

    刨根问底之虚假唤醒 1. 概要 将会以下方式展开介绍: 什么是虚假唤醒 什么原因会导致虚假唤醒(两种原因) 为什么系统内核不从根本上解决虚假唤醒这个"bug"(两个原因) 开发者如 ...

  4. NPTL 线程同步方式

    NPTL提供了互斥体 pthread_mutex_t 类型进行线程同步,防止由于多线程并发对全局变量造成的不正确操作.使用 pthread_mutext_t 对数据进行保护已经可以实现基本的数据同步, ...

  5. Java的LockSupport.park()实现分析

    LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了主要的线程同步原语.LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,仅仅有两个函数: ...

  6. 并行编程条件变量(posix condition variables)

    在整理Java LockSupport.park()东方的,我看到了"Spurious wakeup",通过重新梳理. 首先,可以在<UNIX级别编程环境>在样本: # ...

  7. 4.锁定--Java的LockSupport.park()实现分析

    LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了主要的线程同步原语. LockSupport实际上是调用了Unsafe类里的函数.归结到Unsafe里,仅仅有两个函数: ...

  8. linux同步与通信

    这几天读完了UNP v2,对进程间通信与同步的方式有所了解,现对主要的知识点总结如下: 根据出现的历史,先有的管道,FIFO,信号,然后是systemV IPC,再是后来的Poxis IPC,syst ...

  9. Linux多线程实践(8) --Posix条件变量解决生产者消费者问题

    Posix条件变量 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); int pthread_co ...

随机推荐

  1. SAP中的BOPF(Business Object Processing Framework)

    希望简化你的业务应用开发过程?业务对象处理框架(Business Object Processing Framework,以下简称BOPF)也许可以帮到你. BOPF是SAP Business Sui ...

  2. 一张图,理解JAVA体系结构、运行机制、JVN运行机制、Java平台(初学)

    初学JAVA,学一门语言不仅仅是学其语法,逻辑思维能力,还有每一门语言都有自己独特的一方面,所以才有那么多语言要学啊 = =,所以想要真的学好语言,其编译机制,运行机制多少也要涉猎一些啊.这是初学JA ...

  3. 基于ASP.NET WEB API实现分布式数据访问中间层(提供对数据库的CRUD)

    一些小的C/S项目(winform.WPF等),因需要访问操作数据库,但又不能把DB连接配置在客户端上,原因有很多,可能是DB连接无法直接访问,或客户端不想安装各种DB访问组件,或DB连接不想暴露在客 ...

  4. 可视化编程开发板TurnipBit支持LED亮度可调功能

    微软的makecode编辑器更新至版本v0.12.64.新增LED的可调亮度功能.而作为中文版可视化编程的口袋计算机TurnipBit完全兼容micro:bit,同样支持LED的亮度可调功能. 该项功 ...

  5. tensorflow核心概念和原理介绍

    关于 TensorFlow TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库. 节点(Nodes)在图中表示数学操作,图中的线(edges)则表示 ...

  6. Java框架之Mybatis(一)

    一.Mybatis 简介 Mybatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改 ...

  7. ionic2 App搭建(三)

    cmd命令提示框中进入项目文件夹 运行命令 ionic serve --lab  结构如下图 这里数据是没有接受到的,是因为跨域的问题,解决方案是谷歌浏览器配置跨域指令如下: 配置chrome浏览器允 ...

  8. IdentityServer Topics(3)- 定义客户端

    客户端代表可以从您的身份服务器请求令牌的应用程序. 细节有所不同,但您通常为客户端定义以下常用设置: 一个唯一的客户端ID 一个密钥,如果需要 允许与令牌服务的交互(称为授权类型) 身份或访问令牌被发 ...

  9. Python selenium自动化网页抓取器

    (开开心心每一天~ ---虫瘾师) 直接入正题---Python selenium自动控制浏览器对网页的数据进行抓取,其中包含按钮点击.跳转页面.搜索框的输入.页面的价值数据存储.mongodb自动i ...

  10. GStreamer Windows tutorial demo 开发环境配置

    GStreamer 示例程序在 Windows 环境配置时坑比较多,好不容易配置成功了,写篇文档分享一下安装的关键步骤 官方文档见:https://gstreamer.freedesktop.org/ ...