【问题标题】: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:据我所知,他们在示例中的共享票证池上运行,所以饥饿并不重要。

标签: c++ windows multithreading synchronization


【解决方案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。此外,如果临界区很大,请考虑使用自旋锁。

谢谢

【讨论】:

    猜你喜欢
    • 2017-11-21
    • 1970-01-01
    • 2012-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    相关资源
    最近更新 更多