简单的自定义互斥失败

simple custom made mutex failing

本文关键字:失败 自定义 简单      更新时间:2023-10-16

你能发现代码中的错误吗?门票最终低于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;
编辑:

这个问题的答案很简单,不幸的是,在我花时间修剪示例以发布简化版本之后,我在发布问题后立即找到了答案。叹息. .

我初始化锁定为0。然后,作为不占用总线带宽的优化,如果互斥锁被占用,我不做CAS。

不幸的是,在这种情况下,当锁被取走时,while循环会让第二个线程通过!

很抱歉我多问了一个问题。我以为我不懂windows低级同步原语,但实际上我只是犯了一个简单的错误。

我在你的代码中看到另一个竞争:一个线程可以导致*ctxt.ticket达到0,允许父循环返回并重新设置*ctxt.ticket = eNumThreads而不持有*ctxt.mutex。其他线程现在可能已经持有互斥锁(事实上,它很可能持有)并对*ctxt.ticket进行操作。对于您的简化示例,这只能防止"批"被清晰地分开,但如果在loops循环的顶部有更复杂的初始化(如比单个单词写入更复杂),您可能会看到奇怪的行为。

我发布了一个错误,我认为这是一个合法的多线程问题,但实际上它只是坏的逻辑。我一发帖就解决了这个bug。以下是问题和答案

unsigned int lockedaquired = 0;

我将lockarequired初始化为0,然后添加if语句以跳过执行CAS的昂贵操作。这种优化使它脱离while循环并进入临界区。将代码更改为

unsigned int lockedaquired = 1;

修复问题。在我发现的代码中还有另一个隐藏的问题(我真的不应该再在深夜编码了)。有人注意到临界区if语句后面的分号吗?叹息…

if(*ctxt->ticket > 0);
    (*ctxt->ticket)--;

应该是

if(*ctxt->ticket > 0)

另外,Ben Jackson指出,当我们将票证重置为eNumThreads时,一个线程可能会在临界区内。虽然这在这个示例代码中是完美的,但如果你要将它应用到需要执行更多操作的问题中,它可能不安全,因为线程不是同步运行的,所以如果你将它应用到你的代码中,请记住这一点。

最后注意,如果有人决定使用这段代码来实现互斥锁,请记住你的主驱动线程是空闲的。如果您在临界区执行大量操作,这需要花费大量时间,并且您的票数很高,请考虑放弃您的线程,让其他软件在等待期间使用CPU。另外,如果临界区很大,请考虑使用自旋锁。

谢谢