windows环境下封装条件wait和signal
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的更多相关文章
- Windows环境下32位汇编语言程序设计(典藏版)
Windows环境下32位汇编语言程序设计(典藏版)(含CD光盘1张)(年,经典再现!) 罗云彬 著 ISBN 978-7-121-20759-4 2013年7月出版 定价:99.00元 756页 1 ...
- 第一部分:使用iReport制作报表的详细过程(Windows环境下)
提示:在有些板块,文中的图片看不到,建议到我的blog浏览文章:http://blog.csdn.net/jemlee2002/文章将会涉及3个方面的内容: 第一部分:使用iReport制作报表的详细 ...
- 读书笔记——Windows环境下32位汇编语言程序设计(9)ANSII字符大小写转大写
在罗云彬的<Windows环境下32位汇编语言程序设计>中第321页 ... .const szAllowedChar db '0123456789ABCDEFabcdef',08h .. ...
- Windows环境下Redis
Redis 是一个高性能的key-value数据库, 使用内存作为主存储,数据访问速度非常快,当然它也提供了两种机制支持数据持久化存储.比较遗憾的是,Redis项目不直接支持Windows,Windo ...
- [原]我在Windows环境下的首个Libevent测试实例
libevent对Windows环境也有很好的支持,不过初次学习和编译libevent简单实例,总是有一些陌生感的,只有成功编译并测试了一个实例,才会有恍然大悟的感觉.下面将要讲到的一个实例是我从网上 ...
- 【转】Windows环境下Android NDK环境搭建
原文网址:http://www.metsky.com/archives/525.html 前面介绍Windows下Android 开发环境配置,主要是面向JAVA开发环境,对只做APK上层应用开发人员 ...
- Windows环境下Android NDK环境搭建
前面介绍Windows下Android 开发环境配置,主要是面向JAVA开发环境,对只做APK上层应用开发人员来讲,基本够用了,由于Linux系统的权限限制和Android封装架构限制,很多涉及底层设 ...
- 4.1. 如何在Windows环境下开发Python
4.1. 如何在Windows环境下开发Python 4.1. 如何在Windows环境下开发Python 4.1.1. Python的最原始的开发方式是什么样的 4.1.1.1. 找个文本编辑器,新 ...
- Windows环境下C++中关于文件结束符的问题
参考资料:http://www.cnblogs.com/day-dayup/p/3572374.html 一.前言 在不同的OS环境下,程序中对应的文件结束符有所不一样,根据<C++ Prime ...
随机推荐
- 梯度下降算法以及其Python实现
一.梯度下降算法理论知识 我们给出一组房子面积,卧室数目以及对应房价数据,如何从数据中找到房价y与面积x1和卧室数目x2的关系? 为了实现监督学习,我们选择采用自变量x1.x2的线性函数来评估因变 ...
- pyextend库-merge可迭代对象合并函数
pyextend - python extend lib merge (iterable1, *args) 参数: iterable1: 实现 __iter__的可迭代对象, 如 str, tupl ...
- “Hello World!”团队第五周第五次会议
博客内容: 一.会议时间 二.会议地点 三.会议成员 四.会议内容 五.todo list 六.会议照片 七.燃尽图 八.checkout&push代码 一.会议时间 2017年11月14日 ...
- Scrum7
冲刺阶段的总结 一.各个成员今日完成的任务 组员 任务分工 贡献 林泽宇 团队分工.撰写博客.修改完善需求规格说明书.整理代码规范 李涵 后端架构设计 尹海川 logo设计修改.数据库数据 郏敏杰 课 ...
- 中国剩余定理---FZU 1402 猪的安家
J - 猪的安家 Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Sta ...
- Java GUI 点击按钮退出
import java.awt.*; import java.awt.event.*; public class TestFrameTwo implements ActionListener { Fr ...
- OpenLayers 3 入门教程
OpenLayers 3 入门教程摘要OpenLayers 3对OpenLayers网络地图库进行了根本的重新设计.版本2虽然被广泛使用,但从JavaScript开发的早期发展阶段开始,已日益现实出它 ...
- 1st 结对编程:简易四则运算
结对编程:简易四则运算 功能:进行简易的四则运算,并根据给出的结果判断正误. 实现:使用java的图形化界面实现. 导入包库 package six; import javax.swing.*; im ...
- (五)hadoop系列之__集群搭建SSH无密访问多台机器
免密码ssh设置 现在确认能否不输入口令就用ssh登录localhost: $ ssh localhost 如果不输入口令就无法用ssh登陆localhost,执行下面的命令: . 并修改hosts映 ...
- phpcms前端模板目录与文件结构分析图【templates】
phpcms前端模板目录与文件结构分析图[templates] 原文地址:http://www.iphpcms.net/phpcms-ziliao/2015_14.html