******* 线程互斥与同步大讨论, 有经验的高手请进 *******

freelybird 2002-09-12 02:24:01
加精
我们知道在NT系统下,为了方便程序员实现线程间的互斥与同步,系统提供了三个核心对象:
(1)互斥量
(2)信号量
(3)事件
还有一个非系统核心对象为:
(4)临界区(不能用于进程间的同步)

通过对它们的组合调用,我们很容易满足平时的一般需求,达到同步与互斥的目的.但有时我
们也会发现利用它们很难达到我们要求的同步目的或即使能实现也很复杂. 例如说:对一个
队列或数组实施同步(典型读者写者问题).

我们平时为了简便可以这样做: 把队列数据封装
一个类, 利用临界区互斥, 再加一个整型变量表示队列中数据元素的个数. 这样我们读线程
就可以来轮循它, 当>0时,取一个元素, 否则返回失败; 写线程呢? <最大值时, 写一个元素,
否则返回失败. 当然这样实现同步,它效率低, 因为你要不断地去轮循并检测它.

因此,我们有时经常加一个事件来通知, 这个事件有信号表示队列中有元素,否则无元素,
读线程首先WaitForSingleObject(...), 写线程呢?写完一个元素后如果需要的话则调用
SetEvent(...). 这样虽然能达到较好的性能改善作用. 但它仍不理想, 有可能通知并不及时.

为了达到最理能效果,我们实际上必须要为每个元素设置一个事件(假如这个队列或数组的维数为10,
我们就必须设置10个事件), 这样,每个元素的位置是否有元素,我们可以根据这个event唯一确定.
但这样做对我们来说实在太复杂,而且对同时有多个读线程多个写线程时, 很容易出错并死锁.

所以,我们在实际编程中有这种需求,那就是我们面对很复杂的同步与互斥问题,我们更喜欢通过
调用PV操作(学操作系统时,大家肯定特别熟悉)来实现同步. 其实PV操作解决这些复杂同步问题时,
它往往更显得简单明了,你认为呢?

但现在问题就是: NT系统并没有这种PV操作接口通过API提供给我们. 那我们该怎样获得这种接口或
自己封装PV操作.

例如:在java中, 我就很容易地封装了一个这样的提供PV操作的类,如下(可在C++中,我们却难于做到):
...全文
70 点赞 收藏 54
写回复
54 条回复
mymmsc 2002年12月18日
我来学习一下
回复 点赞
firmamenthy 2002年09月16日
to : freelybird(阿愚)

我是一个初学者,我感觉你的帖子很有建设性,可惜我还没有到达参与讨论或完全领悟的水平。然而,CSDN在我说来是我一个很好的导师,想必大陆不少初学者跟我感觉一样。若如你所说,CSDN已无高手,你何不填补此空缺呢?

感谢你的《***也许是关于CString最全面的总结(1)***》
《***也许是关于CString最全面的总结(2)***》
两文,给我很大的帮助
回复 点赞
freelybird 2002年09月16日
to: In355Hz(好象一条狗)

非常感谢!
回复 点赞
freelybird 2002年09月16日
" 我不太清楚什么事PV操作,以下是仿造你的代码写的Signal类,感觉和Windows提供的Semaphore很相似啊,P操作相当于WaitForSingleObject(hSemaphore, INFINITE), V操作相当于ReleaseSemaphore(hSemaphore, 1, NULL) "

我完全同意你的看法, 实际上它们所完成的功能基本上是一样的.

对于semaphore信号量, 对信号量执行WaitForSingleObject(...)函数, 相当于完成P操作.对信号量执行ReleaseSemaphore(...)函数,相当于执行一个或多个V操作.

所以, 在我们编程中实际上只需使用semaphore就能完成无论多么复杂的同步过程. 我想大家也许忽略semaphore的强大性. 所以俺者出此帖以召大家讨论.
当然, 还有一点就是semaphore并不支持线程安全(一个阻塞的线程必须要另一个线程来唤醒它)

各位csdn的兄弟们, 还要什么想法没有, 否则我要结帖了.
回复 点赞
freelybird 2002年09月16日
to: In355Hz(好象一条狗)

你封装的那个类是达不到要求的. 要不然, 你自己测试看看.

回复 点赞
JamesJiang 2002年09月16日
手头没代码,讲一下思路:
1.构建一线程,使用消息循环,响应3种自定义消息,WM_READ, WM_WRITE, WM_QUITMYSELF。在WM_READ和WM_WRITE的消息里面进行数据操作,因为只有这个线程对数据操作,所以没有同步问题。
2.设计对外的Write和Read接口如以下流程:
。。。
HNADLE hTemp = CreateEvent(...);
PostThreadMessage(m_dwMyThread,WM_WRITE,PointOfData,hTemp);
WaitForSingleObject(hTemp,INFINITE);//阻塞等待
... //已读写结束,可进行其余相关动作。
CloseHandle(hTemp);//关闭这个HANDLE
return;
3.在WM_READ和WM_WRITE的消息里面进行数据操作,因为只有这个线程对数据操作,所以没有同步问题,动作完成以后,执行如下操作:
。。。
SetEvent(HANDLE(msg.wParam));

这只是个框架,剩下的就是封装技巧了。

回复 点赞
freelybird 2002年09月16日
to In355Hz(好象一条狗):

"信号量及PV操作" 是 "操作系统并发" 的物质基础

另外, 大家知道操作系统是怎样实现信号量及PV操作的吗?

我跟大家说说吧!

1 首先, PV操作是一个"原子操作", 即P操作与V操作必须在临界区中执行. 它必须是一个不可分割的执行过程. 操作系统怎么实现它呢? 其实也挺简单,在执行PV操作期间必须屏蔽中断信号,以保证它的"原子操作"
2 操作系统对每个信号量都有一个等待的阻塞队列, 核心的线程调度算法在这些队列中不断地通知或阻塞一个线程.


你封装的那个类,最主要的是你不能保证函数pAction(...)与vAction(...)它是原子操作(即它必须是在临界区中执行). 必须保证每个调用它的线程必须互斥调用. 显然, 你的m_nCount处于一种非常不安全的状态. 你认为呢? 但在java中synchronized修饰符恰好保证了这一点.

第二点, 正如你所说:
"我当时就想了一下,Signal不能像semaphore一样工作,因为不能保证访问的线程数<=2*nCount的值,如果这样,比如6线程争用nCount = 2的Signal,6次pAction就吧nCount变成-4(假设通过pAction的两个线程还没有调用vAction),然后通过的两个线程即使都调用了vAction,另外的4个线程仍阻塞着。"

nCount的值是可以为负数的, 它的意义是:
1 当nCount>0时, 它表示有nCount个资源可以使用, 即允许nCount线程进入访问.
2 当nCount<0时, 它表示有|nCount|个线程在等待队列中, 需要别的线程去唤醒.
3 当nCount=0时, 表示资源正使用完毕.

现在用Signal来做互斥与同步都很容易.
1 互斥时, 设置一个信号量,初始值为1 Signal mutex(1);
现在允许多个线程执行如下的操作. 它总能达到互斥的效果
mutex.pAction();
...
mutex.vAction();

2假如用于同步, 我用java写的那个例子就演示了这一点. 它需要设置三个信号量. 一个用于互斥, 一个用于可以写的资源数,一个用于读的资源数.



回复 点赞
In355Hz 2002年09月16日
to : freelybird(阿愚)
问一下,java的 synchronized/wait/notify 有什么特殊的地方吗?我没有看过java,只是临时查了MSDN看了一下。

我当时就想了一下,Signal不能像semaphore一样工作,因为不能保证访问的线程数<=2*nCount的值,如果这样,比如6线程争用nCount = 2的Signal,6次pAction就吧nCount变成-4(假设通过pAction的两个线程还没有调用vAction),然后通过的两个线程即使都调用了vAction,另外的4个线程仍阻塞着。

不过由于我不太清楚Signal对象的p/vAction到底是如何使用的,当时就简单的仿造你的java代码写了。

如果是用Event模拟Semaphore的行为,CSignal对象的pAction应该这样才对:

BOOL pAction()
{
if (0 > m_nCount)
{
if (m_hEvent == NULL)
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (WAIT_OBJECT_0 != WaitForSingleObject(m_hEvent, INFINITE))
return FALSE;
InterlockedDecrement(&m_nCount);
}
return TRUE;
};

回复 点赞
In355Hz 2002年09月14日
回去想了一下,这上面写的有点Bug, vAction应该这样写才能在保证无线程等待时保持Event处于none-signal状态:
BOOL vAction()
{
if (0 <= InterlockedIncrement(&m_nCount))
{
if (m_hEvent)
return PulseEvent(m_hEvent);
}
return TRUE;
};
回复 点赞
newlily2000 2002年09月13日
gz
回复 点赞
ccnuxjg 2002年09月13日
为什么不用WaitForMultipleObjects
呢?
回复 点赞
freelybird 2002年09月13日
大家看看我在java中实现的pv操作之后, 难道你们不认为它是那么easy吗?

可在SDK api中, 却找不着它的影子(信号量及PV操作). 但我知道它肯定是存在的.
回复 点赞
freelybird 2002年09月13日
"信号量及PV操作" 是 "操作系统并发" 的物质基础

另外, 大家知道操作系统是怎样实现信号量及PV操作的吗?

我跟大家说说吧!

1 首先, PV操作是一个"原子操作", 即P操作与V操作必须在临界区中执行. 它必须是一个不可分割的执行过程. 操作系统怎么实现它呢? 其实也挺简单,在执行PV操作期间必须屏蔽中断信号,以保证它的"原子操作"
2 操作系统对每个信号量都有一个等待的阻塞队列, 核心的线程调度算法在这些队列中不断地通知或阻塞一个线程.

其实就这么简单.
回复 点赞
freelybird 2002年09月13日
其实,这个问题说难也难, 说易也易.

难的是: 信号量及PV操作是在操作系统级别上实现的. 操作系统之所以能够并发, 就一定离不开它. 可win sdk api提供给我们的是更易于使用的更上层的接口api(大家在平时一般更能接受并使用这些函数来同步线程). 可如果我们自己在上层来实现pv操作, 毫无疑问这肯定很难,也违背了规则.

易的是: 上面我已经说了, 操作系统已经实现了这两个函数, 只是它没有暴露给我们. 可我们该怎么能拿到它呢? 这就是我想问的, 有没有人知道???

回复 点赞
alphapiao 2002年09月13日
so hard!i up it.
回复 点赞
freelybird 2002年09月13日
过两个小时后回来,还是这么冷静. 今人伤心呀!

to hlyzy() :
InterlockedExchangeAdd
InterlockedExchange

这两个东西是达不到目的.



回复 点赞
他乡异客1 2002年09月13日
InterlockedExchangeAdd
InterlockedExchange
回复 点赞
wmjcom 2002年09月13日
gz
回复 点赞
freelybird 2002年09月13日
这里越来越次了. 走了.
回复 点赞
freelybird 2002年09月13日
怎样在SDK API中直接获取对信号量的P操作与V操作

回复 点赞
发动态
发帖子
进程/线程/DLL
创建于2007-09-28

6375

社区成员

4.9w+

社区内容

VC/MFC 进程/线程/DLL
社区公告
暂无公告