简单的定制互斥失败

时间:2011-04-28 06:33:45

标签: c++ windows multithreading synchronization

你能发现代码中的错误吗?门票最终低于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低级同步原语,但实际上我只是犯了一个简单的错误。

2 个答案:

答案 0 :(得分:1)

我在你的代码中看到另一个竞赛:一个线程可以使*ctxt.ticket命中0,允许父循环返回并重新设置*ctxt.ticket = eNumThreads而不保持*ctxt.mutex。其他一些线程现在可能已经拥有互斥锁(事实上,它可能确实存在)并在*ctxt.ticket上运行。对于您的简化示例,这只能防止“批处理”被彻底分离,但如果您在loops循环顶部进行了更复杂的初始化(比单个字写入更复杂),您可能会看到奇怪的行为。

答案 1 :(得分:0)

我发布了一个错误,我认为这是一个合法的多线程问题,但实际上它只是错误的逻辑。我发布后立即解决了这个问题。这是问题行和答案

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。另外,如果临界区很大,请考虑使用自旋锁。

谢谢