当我在一组对象上调用WaitForMultipleObjects而另一个线程正在等待使用WaitForSingleObject的那个集合的单个对象时,该线程是首选,并且调用WaitForMultipleObjects的线程永远不会获得CPU时间。
看看这个来源:
#include <windows.h>
#include <cstdio>
#include <unordered_map>
HANDLE hEvt,
hSema;
bool volatile fReleased;
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam );
int main()
{
int const NTHREADS = 10;
HANDLE ahWait[2];
ahWait[0] = ::hEvt = CreateEvent( NULL, FALSE, TRUE, NULL );
ahWait[1] = ::hSema = CreateSemaphore( NULL, 0, 1, NULL );
fReleased = false;
for( int i = 0; i < NTHREADS; i++ )
CreateThread( NULL, 0, LockAndReleaseThread, NULL, 0, NULL );
for( ; ; )
WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ),
std::printf( "main thread is holding lock and received signal\n" ),
::fReleased = false,
SetEvent( ::hEvt );
return 0;
}
char GetID();
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam )
{
for( ; ; )
{
WaitForSingleObject( ::hEvt, INFINITE );
std::printf( "spawned thread with id %c is holding lock\n", (char)GetID() );
if( !::fReleased )
ReleaseSemaphore( ::hSema, 1, NULL ),
::fReleased = true;
Sleep( 1000 );
SetEvent( ::hEvt );
}
return 0;
}
char GetID()
{
static std::unordered_map<DWORD, char> mapTIDsToIDs;
static char nextId = 'A';
DWORD dwThreadId;
if( mapTIDsToIDs.find( dwThreadId = GetCurrentThreadId() ) == mapTIDsToIDs.end() )
return mapTIDsToIDs[dwThreadId] = nextId++;
else
return mapTIDsToIDs[dwThreadId];
}
在此代码中,如果NTHREADS>&gt; = 2,则主线程永远不会获得CPU时间。 我不希望WaitForSingleObject或WaitForMultipleObjects完全公平,但这看起来像是一个概念上的缺陷。
如果我更换
WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ),
与
WaitForSingleObject( ::hSema, INFINITE ),
WaitForSingleObject( ::hEvt, INFINITE ),
主线程获得CPU时间。
[EDIT1]
即使使用Windows 10,该错误也未得到修复。 而使用WaitForMultipleObjects而不是WaitForSingleObject并不会改变这种情况。 产生的线程以循环方式获得CPU时间,如果有睡眠,并且如果我正在进行WaitforSingleObject或WaitForMultipleObjects,则无关紧要。
这里使用关键部分的提示并不能帮助我。
我已经开发了一个条件变量,如果多个线程锁定了condvar并且正在等待condvar被单独调用,那么这个星座就会发生:
#include <windows.h>
#include <cassert>
#include <exception>
#include <intrin.h>
#if !defined(NDEBUG)
#define ONDEBUG(expr) (expr)
#else
#define ONDEBUG(expr) ((void)0)
#endif
inline
DWORD FastGetCurrentThreadId()
{
#if defined(_M_IX86)
return __readfsdword( 0x24 );
#elif defined(_M_AMD64)
return *(DWORD *)(__readgsqword( 0x30 ) + 0x48);
#endif
}
class Exception : public std::exception
{
};
class ResourceException : public Exception
{
};
template <typename TYPE>
inline
TYPE AutoThrowResourceException( TYPE t )
{
if( !t )
throw ResourceException();
return t;
}
class XHANDLE
{
public:
XHANDLE( HANDLE h = NULL );
~XHANDLE();
void CloseHandle();
public:
HANDLE h;
};
inline
XHANDLE::XHANDLE( HANDLE h )
{
this->h = h;
}
inline
XHANDLE::~XHANDLE()
{
BOOL fClosed;
if( h && h != INVALID_HANDLE_VALUE )
fClosed = ::CloseHandle( h ),
assert(fClosed);
}
inline
void XHANDLE::CloseHandle()
{
::CloseHandle( h );
h = NULL;
}
class CondVar
{
public:
CondVar();
~CondVar();
void Enter();
void Wait();
void Release();
void ReleaseAll();
void Leave();
private:
LONGLONG volatile m_llOwnersAndWaiters;
DWORD volatile m_dwRecursionCount;
DWORD volatile m_dwOwningThreadId;
XHANDLE m_xhEvtEnter,
m_xhSemRelease;
private:
static
DWORD Owners( LONGLONG llOwnersAndWaiters )
{
return (DWORD)llOwnersAndWaiters;
}
static
DWORD Waiters( LONGLONG llOwnersAndWaiters )
{
return (DWORD)((DWORDLONG)llOwnersAndWaiters >> 32);
}
};
CondVar::CondVar() :
m_xhEvtEnter( AutoThrowResourceException( ::CreateEvent( NULL, FALSE, FALSE, NULL ) ) ),
m_xhSemRelease( AutoThrowResourceException( ::CreateSemaphore( NULL, 0, 0x7FFFFFFF, NULL ) ) )
{
m_llOwnersAndWaiters = 0;
m_dwRecursionCount = 0;
m_dwOwningThreadId = 0;
}
CondVar::~CondVar()
{
}
void CondVar::Enter()
{
if( m_dwOwningThreadId == FastGetCurrentThreadId() )
{
m_dwRecursionCount++;
return;
}
LONGLONG llOwnersAndWaiters = ::InterlockedIncrement64( &m_llOwnersAndWaiters );
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ));
if( (Owners( llOwnersAndWaiters ) - Waiters( llOwnersAndWaiters )) > 1 )
for( ; ::WaitForSingleObject( m_xhEvtEnter.h, INFINITE ) != WAIT_OBJECT_0; assert(false) );
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == 0);
m_dwOwningThreadId = FastGetCurrentThreadId();
}
void CondVar::Wait()
{
LONGLONG llOwnersAndWaiters;
DWORD dwSavedRecusionCount;
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
m_dwOwningThreadId = 0;
dwSavedRecusionCount = m_dwRecursionCount;
m_dwRecursionCount = 0;
llOwnersAndWaiters = ::InterlockedAdd64( &m_llOwnersAndWaiters, 0x100000000 );
if( Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) )
for( ; !::SetEvent( m_xhEvtEnter.h ); assert(false) );
HANDLE ahWait[2] = { m_xhEvtEnter.h, m_xhSemRelease.h };
for( ; ::WaitForMultipleObjects( 2, ahWait, TRUE, INFINITE ) != WAIT_OBJECT_0; assert(false) );
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == 0);
m_dwOwningThreadId = FastGetCurrentThreadId();
m_dwRecursionCount = dwSavedRecusionCount;
}
void CondVar::Release()
{
LONGLONG llOwnersAndWaiters,
llOwnersAndWaitersPrevOrChanged;
for( llOwnersAndWaiters = m_llOwnersAndWaiters; Waiters( llOwnersAndWaiters ); llOwnersAndWaiters = llOwnersAndWaitersPrevOrChanged )
{
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (llOwnersAndWaitersPrevOrChanged = ::InterlockedCompareExchange64( &m_llOwnersAndWaiters, llOwnersAndWaiters - 0x100000000, llOwnersAndWaiters )) == llOwnersAndWaiters )
{
for (; !::ReleaseSemaphore( m_xhSemRelease.h, 1, NULL ); assert( false ));
break;
}
}
}
void CondVar::ReleaseAll()
{
LONGLONG llOwnersAndWaiters,
llOwnersAndWaitersPrevOrChanged;
for( llOwnersAndWaiters = m_llOwnersAndWaiters; Waiters( llOwnersAndWaiters ); llOwnersAndWaiters = llOwnersAndWaitersPrevOrChanged )
{
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (llOwnersAndWaitersPrevOrChanged = ::InterlockedCompareExchange64( &m_llOwnersAndWaiters, llOwnersAndWaiters & 0x0FFFFFFFF, llOwnersAndWaiters )) == llOwnersAndWaiters )
{
for (; !::ReleaseSemaphore( m_xhSemRelease.h, (LONG)Waiters( llOwnersAndWaiters ), NULL ); assert( false ));
break;
}
}
}
void CondVar::Leave()
{
LONGLONG llOwnersAndWaiters;
LONG lRecursionCount;
ONDEBUG(llOwnersAndWaiters = m_llOwnersAndWaiters);
assert(Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) && m_dwOwningThreadId == FastGetCurrentThreadId());
if( (lRecursionCount = m_dwRecursionCount) != 0 )
{
m_dwRecursionCount = lRecursionCount - 1;
return;
}
m_dwOwningThreadId = 0;
llOwnersAndWaiters = ::InterlockedDecrement64( &m_llOwnersAndWaiters );
if( Owners( llOwnersAndWaiters ) > Waiters( llOwnersAndWaiters ) )
for( ; !::SetEvent( m_xhEvtEnter.h ); assert(false) );
}
/* ---------------------------- unit-test ---------------------------- */
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <map>
DWORD WINAPI ThreadFunc( LPVOID lpvThreadParam );
using namespace std;
struct ThreadResult
{
DWORD dwThreadId;
DWORD dwCounter;
};
CondVar cv;
deque<ThreadResult> dqtr;
bool volatile fStop = false;
int main()
{
int const NTHREADS = 16;
HANDLE ahThreads[NTHREADS];
int i;
std::map<DWORD, DWORD> mTRs;
for( i = 0; i < NTHREADS; i++ )
ahThreads[i] = CreateThread( NULL, 0, ThreadFunc, NULL, 0, NULL );
for( i = 0; i < 10000; i++ )
{
ThreadResult tr;
::cv.Enter();
if( ::dqtr.empty() )
::cv.Wait();
tr = ::dqtr.front();
::dqtr.pop_front();
::cv.Leave();
printf( "Thread: %08X - Number: %d\n", (unsigned)tr.dwThreadId, (unsigned)tr.dwCounter );
if( mTRs.find( tr.dwThreadId ) == mTRs.end() )
mTRs[tr.dwThreadId] = tr.dwCounter;
else
assert((mTRs[tr.dwThreadId] + 1) == tr.dwCounter),
mTRs[tr.dwThreadId] = tr.dwCounter;
}
for( ::fStop = true, i = 0; i < NTHREADS; i++ )
WaitForSingleObject( ahThreads[i], INFINITE );
return 0;
}
DWORD WINAPI ThreadFunc( LPVOID lpvThreadParam )
{
ThreadResult tr;
tr.dwThreadId = FastGetCurrentThreadId();
tr.dwCounter = 0;
for( ; !::fStop; tr.dwCounter++ )
{
::cv.Enter();
::dqtr.push_back( tr );
::cv.Release();
Sleep( 100 );
::cv.Leave();
}
return 0;
}
答案 0 :(得分:0)
我可以重现你的问题,这很奇怪。
为了它的价值,我发现我可以通过独占使用WaitForMultipleObjects()来解决它,并包括一个每线程虚拟对象,即
DWORD WINAPI LockAndReleaseThread( LPVOID lpvThreadParam )
{
HANDLE ahWait[2];
ahWait[0] = ::hEvt;
ahWait[1] = CreateEvent(NULL, TRUE, TRUE, NULL);
for( ; ; )
{
WaitForMultipleObjects(2, ahWait, TRUE, INFINITE );
std::printf( "spawned thread with id %c is holding lock\n", (char)GetID() );
if( !::fReleased )
ReleaseSemaphore( ::hSema, 1, NULL ),
::fReleased = true;
Sleep( 1000 );
SetEvent( ::hEvt );
}
return 0;
}
如果子线程共享一个虚拟对象,那么 似乎不起作用,除非父线程也在等待该对象。因此,虽然它似乎在这种情况下起作用,但它很可能是一种脆弱的方法。
就个人而言,我现在正在寻找另一种解决方案,但在你的情景中,我真的不知道从哪里开始。 (在我的脑海中,我正在考虑一个链接的线程列表,也许还有一些无锁队列。)另一方面,因为你直接从未记录的进程环境块读取线程ID,可靠性和转发兼容性显然不是一个主要的问题,所以也许这种解决方法就足够了。 : - )
您还应该注意到Windows确实有自己的条件变量实现。我不确定你为什么要在这里自己动手。