繁体   English   中英

与互锁操作的线程同步在Visual Studio 2013 C ++本机代码中挂起

[英]Thread synchronization with interlocked ops hangs in Visual Studio 2013 C++ native code

我在C ++中观察线程同步的奇怪行为(64位Windows 8.1,Visual Studio 2013,本机C ++)。

目标是获取对内存中数据结构(“表”)的读访问权。 计数器表tableRIP跟踪当前获取的线程数(有32个线程)。 单个线程也可以具有对表的写访问权。 当线程具有写访问权限时,没有线程可以获得读访问权。 当线程具有写访问cacheLocks将设置CacheLock_WriterWaiting (= 2)位。

这是代码:

volatile long cacheLocks; // bits below
enum CacheLockBit { CacheLock_Table,
                    CacheLock_LRUQ,
                    CacheLock_WriterWaiting,
                    CacheLock_Part
                  };
volatile short tableRIP; // # of readers now in process

Restart:
// Get read access to the table. If we need to write it, it will be changed to write access later.

InterlockedIncrement16(&tableRIP); // assume we will get read access
if(cacheLocks & (1<<CacheLock_WriterWaiting)) // non-zero if a writer is waiting or active
{
    InterlockedDecrement16(&tableRIP); // oops, a writer got in, so we're forbidden
    InterlockedIncrement64(&fc_Wait[0]); // counter for diagnostic purposes
    Wait(waitMs); // waitMs is a constant 1 (msec)
    goto Restart;
}
// Now we're a valid reader, and writer can't proceed till we've finished

令人费解的是,程序在这个循环中挂起。 当我使用调试器并单步执行循环(下面的详细信息)时,它会立即退出。 它表现为AS IF变量cacheLocks不易变(但是,正如您从下面的汇编代码中看到的那样)。

在我看的时候,只有一个线程处于活动状态(这个)。 其他31个人正在等待这个完成,并且还有一个活动的UI线程,它不访问此数据结构。

由于这是一个发布版本,我正在使用汇编代码和直接查看内存进行调试。 下面是代码,但是使用汇编代码,如调试器中所示:

Restart:
// Get read access to the table. If we need to write it, it will be changed to write access later.

InterlockedIncrement16(&tableRIP); // assume we will get read access
00007FF789DF9970  lock inc    word ptr [rbx+2A4h] // (1) before 0, after 1 
if(cacheLocks & (1<<CacheLock_WriterWaiting)) // non-zero if a writer is waiting or active
00007FF789DF9978  mov         eax,dword ptr [rbx+2A0h]  // (5) eax -> 0
00007FF789DF997E  test        al,4  
00007FF789DF9980  je          $Restart+2Fh (7FF789DF999Fh)  
{
    InterlockedDecrement16(&tableRIP); // oops, a writer got in, so we're forbidden
00007FF789DF9982  lock dec    word ptr [rbx+2A4h]  
    InterlockedIncrement64(&fc_Wait[0]);
00007FF789DF998A  lock inc    qword ptr [rbx+1E0h]  
    Wait(waitMs);
00007FF789DF9992  mov         ecx,dword ptr [rbx+290h]  
00007FF789DF9998  call        Concurrency::wait (7FF789FB1000h) // (3) debugger breaks here  
    goto Restart; // (4)
00007FF789DF999D  jmp         FileCache::CacheInsureLoaded+0A6h (7FF789DF9966h)  
}
// Now we're a valid reader, and writer can't proceed till we've finished

当我使用调试器“中断”程序时,该线程在系统例程Concurrency::wait 我走出去,直到我的代码中的(4)。 然后我检查rbx+2A4h (即tableRIP )的内存,它是0.单步执行inc它是1,正如预期的那样。 检查rbx+2A0h (即cacheLocks )的内存,它在位置(5)为0(即没有写入器激活)。 另一步,我们跳转到$Restart+2Fh ,退出循环。

程序在循环中旋转数小时,直到调试器用于单步执行汇编代码。 您可以从上面的代码中看到C ++编译器已将变量tableRIPcacheLocks正确地视为volatile:它每次都从内存中加载它们。 我注意到这两个变量在内存中是相邻的。 我需要考虑一些硬件功能吗? 处理器是Intel Core i7-4771。

编辑:在回复我发布的问题时,这里有更详细的代码,显示了cacheLocks所有操作。 还有一些cachePart[iPart]锁的使用,它是缓冲区的细粒度锁定; 这与锁定表无关,并且并未显示缓冲区锁定的所有用法。

与锁定无关的代码部分已由// PROCESS替换。

// Data members of class FileCache:
volatile long cacheLocks; // bits below
enum CacheLockBit { CacheLock_Table,
                    CacheLock_LRUQ,
                    CacheLock_WriterWaiting,
                    CacheLock_Part
                  };
volatile short tableRIP; // # of readers now in process

// Code from class FileCache:
Restart:
    // Get read access to the table. If we need to write it, it will be changed to write access later.

    InterlockedIncrement16(&tableRIP); // assume we will get read access
    if(cacheLocks & (1<<CacheLock_WriterWaiting)) // non-zero if a writer is waiting or active
    {
        InterlockedDecrement16(&tableRIP); // oops, a writer got in, so we're forbidden
        InterlockedIncrement64(&fc_Wait[0]);
        Wait(waitMs);
        goto Restart;
    }
    // Now we're a valid reader, and writer can't proceed till we've finished


// PROCESS
    if(iPart!=bs_NotInCache && iPart!=bs_Writing) // i e, it's in cache and not in process of being written
    {
        if(cachePart[iPart].nLocks[lt_FileRead]==rl_FileReadLock)
        {
            // Another thread is setting a file read lock on this part, for unknown ix. Must wait, in case it's for this ix.
            InterlockedDecrement16(&tableRIP); // reader in no longer in progress
            InterlockedIncrement64(&fc_Wait[1]);
            Wait(waitMs);
            goto Restart;
        }

        // Lock this cache part
        while(InterlockedBitTestAndSet(&cachePart[iPart].partLocks, CacheLock_Part)) // returns 1 if bit (lock) was already set
        {
            InterlockedIncrement64(&fc_Wait[9]);
            Wait(waitMs);
        }

        while(cachePart[iPart].nLocks[lt_FileRead]!=0)
        {
            // Another thread is reading the desired block. Must wait till that is complete, then start over.
            InterlockedBitTestAndReset(&cachePart[iPart].partLocks, CacheLock_Part); // release the mutex
            InterlockedDecrement16(&tableRIP); // reader in no longer in progress
            InterlockedIncrement64(&fc_Wait[2]);
            Wait(waitMs);
            goto Restart;
        }

// PROCESS
        InterlockedDecrement16(&tableRIP); // reader in no longer in progress

        return iPart;
    }

    else if(partFromBlock[block]==bs_Writing)
    {
        // Another thread is writing this block--must wait till it's finished, then try again
        if(debugPartFromBlock)
            PartFromBlockCheck(workerThreadNum);
        InterlockedDecrement16(&tableRIP); // reader in no longer in progress
        InterlockedIncrement64(&fc_Wait[6]);
        Wait(waitMs);
        goto Restart;
    }

    // Desired block isn't in cache; must read it from file.
    // Now we need a write lock.
    InterlockedDecrement16(&tableRIP); // we're no longer a reader
    if(InterlockedBitTestAndSet(&cacheLocks, CacheLock_WriterWaiting)) // get 'writer active' status
    {
        InterlockedIncrement64(&fc_Wait[7]);
        Wait(waitMs);
        goto Restart;
    }

    // We have 'writer active' set, but we need to wait for all readers to finish
    while(tableRIP > 0)
    {
        InterlockedIncrement64(&fc_Wait[8]);
        Wait(waitMs);
    }

    // Now this thread is the only one accessing the table
    iPart=CacheFill(workerThreadNum, clt, ix, block, lType);
    if(iPart<0)
    {
        // CacheFill was unable to lock the part
        unsigned char locks=InterlockedBitTestAndReset(&cacheLocks, CacheLock_WriterWaiting); // no longer writer active
        InterlockedIncrement64(&fc_Wait[3]);
        Wait(waitMs);
        goto Restart;
    }

    // Convert the file read lock to the desired lock type. Current lock should be exactly 1 file read.
    long long locks=InterlockedCompareExchange64(&cachePart[iPart].allLocks,1LL<<(lType*16),LOCKPARTS(0,0,1,0));

    return iPart;
}

// Read a block which contains the desired bit into a cache part. Table is 'writer active'.
// If the cache has been modified, write it first.
// Returns the part #, and turns off 'writer active'.
int FileCache::CacheFill(const int workerThreadNum, const CacheLocType clt, const DBIndex ix, const unsigned long long block, const LockType lType)
{
    int retries=0;
Restart:
// PROCESS
    // Found an eligible part--try to lock it. To succeed, there must be no locks of any kind on the cache part
    long long locks=InterlockedCompareExchange64(&cachePart[iPartLRU].allLocks,LOCKPARTS(0,0,rl_FileReadLock,0),0);
    if(locks!=0)
    {
        if(retries>10)
            return -2; // unable to find an available buffer; wait till one becomes available
        InterlockedIncrement64(&fc_Wait[4]);
        Wait(waitMs);
        retries++;
        goto Restart; // try for another
    }

// PROCESS
    locks=InterlockedBitTestAndReset(&cacheLocks, CacheLock_WriterWaiting); // no longer writer active

// PROCESS
    // Now the old block (if any) is gone, so we can remove it from the table
    if(oldBase!=NoIndex)
    {
        // Lock table again. It was unlocked so other threads could run while we were writing.
        // However, another writer is not allowed to remove the 'oldBase' part.
        while(InterlockedBitTestAndSet(&cacheLocks, CacheLock_WriterWaiting)) // returns 1 if bit (lock) was already set
        {
            InterlockedIncrement64(&fc_Wait[5]);
            Wait(waitMs);
        }
        // No other threads can access the table, except readers in progress. We have to wait for those to finish.
        while(tableRIP > 0)
        {
            // No other threads can access the table, except readers in progress. We have to wait for those to finish.
            InterlockedIncrement64(&fc_Wait[10]);
            Wait(waitMs);
        }
        unsigned char locks=InterlockedBitTestAndReset(&cacheLocks, CacheLock_WriterWaiting); // no longer writer active
    }

// PROCESS
    return iPart;
}

附加信息:

参考初始循环,看起来正在发生的是Wait(1 msec)永远不会返回。 应该发生的是,在短暂的等待之后,线程再次检查cacheLocks

当pgm与调试器'断开'时,继续,挂起的线程恢复,找到cacheLocks = 0,并退出循环。

等待功能就是

void Wait(const int msec)
{
    Concurrency::wait(msec);
    return;
}

所有工作线程都以优先级“最低”运行,但主UI线程除外,它处于“正常”状态。 我找不到有关Concurrency::wait(msec)和Windows线程调度程序如何工作的任何信息。

也许有人可以解释为什么会这样?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM