简单的定制互斥锁失败
Posted
技术标签:
【中文标题】简单的定制互斥锁失败【英文标题】:simple custom made mutex failing 【发布时间】:2011-04-28 06:33:45 【问题描述】:你能发现代码中的错误吗?门票最终低于 0 导致长时间停顿。
struct SContext
volatile unsigned long* mutex;
volatile long* ticket;
volatile bool* done;
;
static unsigned int MyThreadFunc(SContext* ctxt)
// -- keep going until we signal for thread to close
while(*ctxt->done == false)
while(*ctxt->ticket) // while we have tickets waiting
unsigned int lockedaquired = 0;
do
if(*ctxt->mutex == 0) // only try if someone doesn't have mutex locked
// -- if the compare and swap doesn't work then the function returns
// -- the value it expects
lockedaquired = InterlockedCompareExchange(ctxt->mutex, 1, 0);
while(lockedaquired != 0); // loop while we didn't aquire lock
// -- enter critical section
// -- grab a ticket
if(*ctxt->ticket > 0);
(*ctxt->ticket)--;
// -- exit critical section
*ctxt->mutex = 0; // release lock
return 0;
调用函数等待线程完成
for(unsigned int loops = 0; loops < eLoopCount; ++loops)
*ctxt.ticket = eNumThreads; // let the threads start!
// -- wait for threads to finish
while(*ctxt.ticket != 0)
;
done = true;
编辑:
这个问题的答案很简单,不幸的是,在我花时间精简示例以发布简化版本后,我在发布问题后立即找到了答案。叹息..
我将 lockaquired 初始化为 0。然后作为不占用总线带宽的优化,如果使用互斥体,我不执行 CAS。
不幸的是,在这种情况下,当锁被占用时,while 循环会让第二个线程通过!
很抱歉有额外的问题。我以为我不了解 Windows 低级同步原语,但实际上我只是犯了一个简单的错误。
【问题讨论】:
能否提供第二个代码示例的变量声明。 @ereOn 会的,谢谢提醒! @coderdave:对你的问题表示赞同:) 另外一个问题是,如果你有很多线程,那些等待锁空闲的线程会随机看到下一次解锁。不幸的线程可能不得不永远等待。 @Bo:据我所知,他们在示例中的共享票证池上运行,所以饥饿并不重要。 【参考方案1】:我在您的代码中看到另一场比赛:一个线程可能导致 *ctxt.ticket
达到 0,从而允许父循环返回并重新设置 *ctxt.ticket = eNumThreads
而无需持有 *ctxt.mutex
。其他一些线程现在可能已经持有互斥锁(事实上,它可能确实如此)并在*ctxt.ticket
上运行。对于您的简化示例,这只会阻止“批次”被完全分离,但如果您在 loops
循环的顶部有更复杂的初始化(如比单个单词写入更复杂),您可能会看到奇怪的行为。
【讨论】:
是的,这是真的!在这个简化的示例中,虽然它是安全的,但这不是复制和粘贴到真实代码的好模型。感谢您的精彩评论。【参考方案2】:我发布了一个错误,我认为这是一个合法的多线程问题,但实际上这只是糟糕的逻辑。我一发布就解决了这个错误。以下是问题线和答案
unsigned int lockedaquired = 0;
我将 lockaquired 初始化为 0,然后在我添加了一个 if 语句以跳过执行 CAS 的昂贵操作之后。这种优化导致它退出 while 循环并进入关键部分。将代码更改为
unsigned int lockedaquired = 1;
解决问题。我发现代码中还有另一个隐藏的问题(我真的不应该再深夜编码了)。有人注意到关键部分中 if 语句后的分号吗?叹息……
if(*ctxt->ticket > 0);
(*ctxt->ticket)--;
应该是这样的
if(*ctxt->ticket > 0)
此外,Ben Jackson 指出,当我们将票证重置为 eNumThreads 时,一个线程可能会在临界区中。虽然这在此示例代码中非常好,但如果您要将它应用于需要执行更多操作的问题,它可能不安全,因为线程没有以同步方式运行,因此如果您将其应用于您的代码。
最后一点,如果有人决定将这段代码用于他们自己的互斥锁实现,请记住您的主驱动线程正在空闲。如果您在关键部分执行需要大量时间的大型操作并且您的票数很高,请考虑让出您的线程以让其他软件在等待时使用 CPU。此外,如果临界区很大,请考虑使用自旋锁。
谢谢
【讨论】:
以上是关于简单的定制互斥锁失败的主要内容,如果未能解决你的问题,请参考以下文章