linux 环境有提供好的pthread_cond_wait() 和 phread_signal()、pthread_broadcast()

windows需要自己封装,利用semophore控制线程等待和释放,先简单谈一下设计好后api该

如何使用。

假设我们封装好条件变量等待函数名字叫做wait(Mutex& mutex),Mutex是之前我们封装的

条件变量,文章最下边会给出这些文件的下载地址,在这里读者当做linux 的mutex即可。我们

封装的释放函数为signal(),广播函数为broadcast。

判断等待条件变量和逻辑处理如下:

Lock(mutex);

while(条件不满足)

{

   wait(mutex);

}

todo...;

UnLock(mutex);

激活条件变量如下:

Lock(mutex);

  todo ...; 

  if(条件满足)

  {

    signal();/broadcast();

  } 

signal();

UnLock(mutex);

Condition 是我们封装的条件变量类

这是封装好api后调用规则,那么先考虑wait内部的基本形式

void Condition::wait(Mutex &mutex)

{

  //1 Condition 类中表示阻塞线程数 

  mblocked ++;

  //2 解锁,释放互斥量

  UnLock(mutex);

  //3 阻塞等待 mQueue为信号量

   res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);

   //4 做一些判断和逻辑处理

  //5 加锁

 Lock(mutex);

}

wait内部记录一个阻塞的线程数mblocked,mblocked 是我们封装Condition类的成员变量,

然后释放外部的互斥量,然后调用阻塞函数,等待signal唤醒。

当WaitForSingleObject获取信号后会继续执行,做一些逻辑判断,最后将mutex锁住。

这里用到的mQueue是一个信号量,用信号量可以接受多个唤醒和控制线程唤醒数量。

下面是条件变量释放的函数,我们先做只是放一个条件变量的api

void Condition::signal()

{

  //1阻塞的线程减少

  mblocked --;

  //2将激活的信号个数设置为1

  signals = 1;

  //3

  if (signals)

  {

    //释放信号量
    res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mQueue), signals, 0);
    ASSERT(res);
  }

}

先不要着急往下写,考虑下这么做真的合适么?

首先之前设计过外部调用

  if(条件满足)

  {

    signal();/broadcast();

  } 

这个只要条件满足就可以激活,所以我们只用mblocked表示阻塞线程数是不够的,当信号量被激活很多没有被消耗的情况下

就需要统计当前可用的资源数,那么就在Condition类添加mWait表示当前可用的信号量个数。除此之外,考虑这样一种情况,

当条件不满足的时候 线程A调用void wait(Mutex &mutex)函数,wait函数先解锁再阻塞,对应wait中第2,3步骤。而另一个

线程B当条件满足时调用 signal函数激活之前阻塞的线程A,对应signal函数中第3步 。原阻塞线程A因为捕获到信号量,所以

一次走到wait中第4、5步。由于第4和第5步之间没有加锁保护,所以这一阶段用到的类的成员变量都是不安全的。所以在第3

和第4之间加一个互斥锁,第5步之后释放这个互斥锁。同样的道理,为了避免此时signal内部调用类的成员变量造成数据不一致

所以signal内部也需要加锁,在signal内部第1步之前加锁,第3步之后解锁,或者第3步之前解锁都可以。我觉得在第三步之前

释放会好一些,在释放信号量之前解锁,避免死锁。所以添加一个成员变量mMutex

用于部分代码互斥。

那么改良后我们的函数如下:

void
Condition::wait(Mutex& mutex)
{
  #ifndef WIN32
  int ret = pthread_cond_wait(&mId, mutex.getId());
  ASSERT(ret == 0);
  #else
//1
mBlocked++;
//2
  mutex.unlock();   int res = 0;
     //3
   res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);
  ASSERT(res == WAIT_OBJECT_0); //用于暂时存储mWaiting的数值
   unsigned wasWaiting = 0;
   //4
  res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
  ASSERT(res == WAIT_OBJECT_0);
  wasWaiting = mWaiting;
   //5
   res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
  ASSERT(res);
    //6
   mutex.lock();
#endif
}

  步骤也做了相应的调整。

void
Condition::signal ()
{
#ifndef WIN32
int ret = pthread_cond_signal(&mId);
ASSERT(ret == );
#else
unsigned signals = ; int res = ;
  //1
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
  //2
if (mWaiting != )
{
if (mBlocked == )
{
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);
return;
} ++mWaiting;
--mBlocked;
signals = ;
}
else
{
signals = mWaiting = ;
--mBlocked; }
  //3
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);
  //4
if (signals)
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mQueue), signals, );
ASSERT(res);
}
#endif
}

改良后更新了步骤,注释的就是步骤,方便接下来讨论这两段代码的隐患,因为仅仅这些还不够。目前现总结下mMutex作用:

1mMutex用于signal函数内部和wait函数 获取信号量之后的代码互斥,保护类的常用变量。

2当不同的线程调用wait等待后获得激活时,mMutex保证获得信号量之后的操作是互斥的,安全的。

由于调用wait函数之前需要加外部的互斥锁,所以不同的线程调用wai函数时第一步的mBlocked++是互斥的,不会出错。

唯一有可能出错的是那种情况呢?

就是当signal发出信号后,当前有一个因为调用wait阻塞的线程A捕获到该信号,进入第四步,修改或者访问mBlocked变量的值,

与此同时有线程A调用wait函数,此时会进入wait内部第一步mBlocked++,多线程修改和读取mBlocked会造成数据混乱,

所以此时需要在第一步之前加锁,第2步之前解锁,因此添加单个信号量mGate,用于控制当有线程处于解锁状态处理mBlocked等

类成员时,其他线程进入wait修改mBlocked值。

这个res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);可以放在wait函数第4步之后,当第4步获得互斥

资源后,阻塞等待获取mGate信号,如果没获得需要等待别的线程释放mGate,如果此时mGate不被释放造成mMutex死锁。所以

别的线程中先调用 WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);后调用WaitForSingleObject mMutex会造成

死锁。需要特别注意。如果规避了这一点,那么就可以避免死锁。所有情况都对mGate互斥访问并不友好,出现之前讨论的情况只有一种:

就是当前应用程序中至少有一个线程处于等待,而signal释放信号后,某一个等待的线程继续执行4后面的操作,外界有新的线程调用wait时

修改mBlocked会出错。所以只需要在signal函数中判断当mWaiting数量为0时对mGate加锁,mWait根据不同情况进行对mGate进行释放。

修改后的代码如下:

先封装一个小函数:

void
Condition::enterWait ()
{
int res = ;
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
++mBlocked;
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , );
ASSERT(res);
}

对mBlocked起到保护作用

void
Condition::wait(Mutex& mutex)
{
#ifndef WIN32
int ret = pthread_cond_wait(&mId, mutex.getId());
ASSERT(ret == );
#else
//1
  enterWait();
  //2
mutex.unlock(); int res = ;
  //3
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);
ASSERT(res == WAIT_OBJECT_0); unsigned wasWaiting = ;
unsigned wasGone = ;
  //4
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
wasWaiting = mWaiting;
wasGone = mGone;
  //signal释放资源后,mWaiting 至少为1
if (wasWaiting != )
{
    //判断mWaiting 数量为1
if (--mWaiting == )
{
       //如果当前没有阻塞线程则释放mGate
if (mBlocked != )
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , ); // open mGate
ASSERT(res);
wasWaiting = ;
} }
}
//5
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);
  //6
mutex.lock();
#endif
}

对应的signal函数:

void
Condition::signal ()
{
#ifndef WIN32
int ret = pthread_cond_signal(&mId);
ASSERT(ret == );
#else
unsigned signals = ; int res = ;
  //1
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
  
if (mWaiting != )
{
    //当前有空闲的信号量并且没由阻塞的线程
if (mBlocked == )
{
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);
return;
}
    //如果由阻塞的线程,那么阻塞数量--
++mWaiting;
--mBlocked;
signals = ;
}
else
{
//2当空闲的信号量为0时,互斥获得mGate
     res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
     //3
if (mBlocked )
{
 //如果当前有线程阻塞那么更新计数
signals = mWaiting = ;
--mBlocked;
}
else
{
       //由于用户外部不判断条件是否成立多次调动signal,此处不处理直接释放mGate
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , );
ASSERT(res);
}
}

  //4
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);

  //5
if (signals)
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mQueue), signals, );
ASSERT(res);
}
#endif
}

到目前为止,对于共享对象的保护和同步都做的比较完善了,还要注意一个问题就是虚假唤醒。这是

操作系统可能出现的一种情况,所以需要添加虚假唤醒的逻辑用mGone成员变量表示出错的或是虚假唤醒的线程数

最终代码如下:

void
Condition::wait(Mutex& mutex)
{
#ifndef WIN32
int ret = pthread_cond_wait(&mId, mutex.getId());
ASSERT(ret == );
#else
enterWait(); mutex.unlock(); int res = ;
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);
ASSERT(res == WAIT_OBJECT_0); unsigned wasWaiting = ;
unsigned wasGone = ; res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
wasWaiting = mWaiting;
wasGone = mGone;
if (wasWaiting != )
{
if (--mWaiting == )
{
if (mBlocked != )
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , ); // open mGate
ASSERT(res);
wasWaiting = ;
}
else if (mGone != )
{
mGone = ;
}
}
}
else if (++mGone == (ULONG_MAX / ))
{
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
mBlocked -= mGone;
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , );
ASSERT(res);
mGone = ;
}
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res); if (wasWaiting == )
{
for (; wasGone; --wasGone)
{
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
}
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , );
ASSERT(res);
} mutex.lock();
#endif
}

wait部分添加了mGone的处理,当mWaiting数量为0进入

 res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
需要对mGone++表示虚假唤醒的线程数量
if (++mGone == (ULONG_MAX / 2))
{
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
mBlocked -= mGone;
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), 1, 0);
ASSERT(res);
mGone = 0;
}
通过mGate对mBlocked保护起来,当唤醒的个数超过指定值会把多余的mblocked去掉并且把
虚假唤醒数量置空。举个例子,当mBLocked为1时该线程被虚假唤醒,那么mGone变为1,由于是
虚假唤醒,用户在外部调用wait函数时通过while循环判断条件不满足再次进入wait中enterGate
函数对mBlocked自增,此时mBlocked数量为2,所以当冗余的mBlocked超过指定值,就回去掉
这些mBlocked并将mGone置空。
if (wasWaiting == 1)
{
for (; wasGone; --wasGone)
{
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mQueue), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
}
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), 1, 0);
ASSERT(res);
}
该函数判断Condation类的mWating变量有1变为0,并且阻塞的线程数为0,因为如果用户没有在外边调用while
判断条件导致虚假唤醒引起逻辑错误,所以为了起到保护作用对那些因为虚假唤醒错过的信号进行资源占用,
直到信号量都被释放后才进入mGate释放。举一个例子
如果外部调用
Lock(mutex);
if(条件不满足)
{
  wait(mutex);
  
}
//逻辑处理
    ...
UnLock(mutex);
当wait执行退出后会执行逻辑,而没有while判断条件是否真的满足。所以我们要对信号量进行控制,保证信号量
数量正确。并且和mBlocked,mWait,等一致。
下面是signal函数最终版本
void
Condition::signal ()
{
#ifndef WIN32
int ret = pthread_cond_signal(&mId);
ASSERT(ret == );
#else
unsigned signals = ; int res = ;
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mMutex), INFINITE);
ASSERT(res == WAIT_OBJECT_0); if (mWaiting != )
{
if (mBlocked == )
{
res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res);
return;
} ++mWaiting;
--mBlocked;
signals = ;
}
else
{
res = WaitForSingleObject(reinterpret_cast<HANDLE>(mGate), INFINITE);
ASSERT(res == WAIT_OBJECT_0);
if (mBlocked > mGone)
{
if (mGone != )
{
mBlocked -= mGone;
mGone = ;
}
signals = mWaiting = ;
--mBlocked;
}
else
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mGate), , );
ASSERT(res);
}
} res = ReleaseMutex(reinterpret_cast<HANDLE>(mMutex));
ASSERT(res); if (signals)
{
res = ReleaseSemaphore(reinterpret_cast<HANDLE>(mQueue), signals, );
ASSERT(res);
}
#endif
}
同样的道理
  if (mBlocked > mGone)
{
if (mGone != 0)
{
mBlocked -= mGone;
mGone = 0;
}
signals = mWaiting = 1;
--mBlocked;
}
这个逻辑就是处理当虚假唤醒的mBlocked和mGone等数据准确性。
因为如果是虚假唤醒,用户通过while(条件不满足)这个方式继续调用wait
会导致mBlocked++,假设就一个线程处于阻塞并且因为虚假唤醒通过while循环
重新调用wait函数,而此时mGone比mBlocked小1,所以mBlocked - mGone就是
更新差值给mBlocked,这是真正的处于阻塞的线程数量。 下面是代码下载地址: http://download.csdn.net/detail/secondtonone1/9658645 代码效果测试截图:


谢谢关注我的微信公众号:

												

windows环境下封装条件wait和signal的更多相关文章

  1. Windows环境下32位汇编语言程序设计(典藏版)

    Windows环境下32位汇编语言程序设计(典藏版)(含CD光盘1张)(年,经典再现!) 罗云彬 著 ISBN 978-7-121-20759-4 2013年7月出版 定价:99.00元 756页 1 ...

  2. 第一部分:使用iReport制作报表的详细过程(Windows环境下)

    提示:在有些板块,文中的图片看不到,建议到我的blog浏览文章:http://blog.csdn.net/jemlee2002/文章将会涉及3个方面的内容: 第一部分:使用iReport制作报表的详细 ...

  3. 读书笔记——Windows环境下32位汇编语言程序设计(9)ANSII字符大小写转大写

    在罗云彬的<Windows环境下32位汇编语言程序设计>中第321页 ... .const szAllowedChar db '0123456789ABCDEFabcdef',08h .. ...

  4. Windows环境下Redis

    Redis 是一个高性能的key-value数据库, 使用内存作为主存储,数据访问速度非常快,当然它也提供了两种机制支持数据持久化存储.比较遗憾的是,Redis项目不直接支持Windows,Windo ...

  5. [原]我在Windows环境下的首个Libevent测试实例

    libevent对Windows环境也有很好的支持,不过初次学习和编译libevent简单实例,总是有一些陌生感的,只有成功编译并测试了一个实例,才会有恍然大悟的感觉.下面将要讲到的一个实例是我从网上 ...

  6. 【转】Windows环境下Android NDK环境搭建

    原文网址:http://www.metsky.com/archives/525.html 前面介绍Windows下Android 开发环境配置,主要是面向JAVA开发环境,对只做APK上层应用开发人员 ...

  7. Windows环境下Android NDK环境搭建

    前面介绍Windows下Android 开发环境配置,主要是面向JAVA开发环境,对只做APK上层应用开发人员来讲,基本够用了,由于Linux系统的权限限制和Android封装架构限制,很多涉及底层设 ...

  8. 4.1. 如何在Windows环境下开发Python

    4.1. 如何在Windows环境下开发Python 4.1. 如何在Windows环境下开发Python 4.1.1. Python的最原始的开发方式是什么样的 4.1.1.1. 找个文本编辑器,新 ...

  9. Windows环境下C++中关于文件结束符的问题

    参考资料:http://www.cnblogs.com/day-dayup/p/3572374.html 一.前言 在不同的OS环境下,程序中对应的文件结束符有所不一样,根据<C++ Prime ...

随机推荐

  1. openvpn部署

    原文发表于cu:2016-03-29 参考文档: 安装:http://qicheng0211.blog.51cto.com/3958621/1575273 安装:http://www.ipython. ...

  2. 论文笔记:Visualizing and Understanding Convolutional Networks

    2014 ECCV 纽约大学 Matthew D. Zeiler, Rob Fergus 简单介绍(What) 提出了一种可视化的技巧,能够看到CNN中间层的特征功能和分类操作. 通过对这些可视化信息 ...

  3. java面向对象的有序数组和无序数组的比较

    package aa; class Array{ //定义一个有序数组 private long[] a; //定义数组长度 private int nElems; //构造函数初始化 public ...

  4. Intense Heat(前缀和或尺取)

    The heat during the last few days has been really intense. Scientists from all over the Berland stud ...

  5. vs快捷键代码格式化或代码对齐名字

    开发人员,换个电脑后环境要重装,vs的环境也需要重新设置. 快捷键需要重新设置,插件也需要重装,在这里备注下,换个环境就可以直接用了. 由于vs不同版本,代码对齐或者代码格式化的快捷键都不一样,所以导 ...

  6. Python_1

    转载来源:http://www.cnblogs.com/wupeiqi/articles/4906230.html python内部执行过程如下: python解释器在加载 .py 文件中的代码时,会 ...

  7. 关于Filter的一点误解

    之前一直以为请求达到Web应用时,经过过滤器1,过滤器2……,处理后产生响应再经过过滤器n……过滤器2,过滤器1.这样的阐述似乎没有问题,但我的理解却有问题.比如过滤器1的doFilter方法执行了一 ...

  8. 第5章 首次登录与在线求助man page

    首次登录系统 centos默认图像界面为GNOME. Linux默认情况下会提供6个Terminal来让用户登录,切换方式为ctrl+alt+[F1-F6],系统将这六个操作界面命名为tty1-tty ...

  9. js实现轮播功能

    先上图,效果大概就是这样子: 实现的功能: 1.鼠标经过第几个正方形,就要展示第几张图片,并且正方形的颜色也发生变化 2.图片自动轮播,(这需要一个定时器) 3.鼠标经过图片,图片停止自动播放(这需要 ...

  10. 1014 我的C语言文法定义与C程序推导过程

    程序> -> <外部声明> | <程序> <外部声明> <外部声明> -> <函数定义> | <声明> < ...