WaitForMultipleObjects挨饿

时间:2016-06-27 18:43:02

标签: winapi

当我在一组对象上调用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;
}

1 个答案:

答案 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确实有自己的条件变量实现。我不确定你为什么要在这里自己动手。