[英]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 ++编译器已将变量tableRIP
和cacheLocks
正确地视为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.