2016-06-27 12 views
1

オブジェクトのセットでWaitForMultipleObjectsを呼び出し、別のスレッドがWaitForSingleObjectでそのセットの単一のオブジェクトを待っているとき、このスレッドが優先され、WaitForMultipleObjectsを呼び出すスレッドは決してCPU時間を取得しません。このソースでWaitForMultipleObjectsが飢えている

ルック:このコードで

#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の値が> = 2 であれば、メインスレッドがCPU時間を取得したことがない私はのWaitForSingleObjectまたはたWaitForMultipleObjectsは完全に公平であることを期待していませんが、これは概念的な欠陥のように私に見える。

私は

WaitForSingleObject(::hSema, INFINITE), 
WaitForSingleObject(::hEvt, INFINITE), 

WaitForMultipleObjects(2, ahWait, TRUE, INFINITE), 

を交換する場合は、メインスレッドがCPU時間を取得します。

[EDIT1]

バグは、Windows 10 でも、固定されておらず、代わりのWaitForSingleObjectのたWaitForMultipleObjectsを行うと、状況は変わりません。 そして、生成されたスレッドはラウンドロビン方式でCPU時間を取得し、スリープがあるかどうか、およびWaitforSingleObjectまたはWaitForMultipleObjectsを実行しているかどうかは関係ありません。

ここで重要なセクションを使用しているヒントは私を助けません。

私はこの星座は、複数のスレッドがcondvarをロックされ、1つはsingalledする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

私はあなたの問題を再現することができますが、それ奇妙です。独占的に何が価値があるために

、私は)私は(た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には、条件変数の独自の実装があることにも注意してください。なぜあなたがここであなた自身を圧倒しているのか分かりません。

+0

私を見てください[編集] –

+0

@BonitaMontero、gah、はい、あなたはかなり正しいです。私が実験していたコードは、私が思っていたことを全くしていなかった。私があなたのコードに戻って、私が示唆していた最小限の変更を加えたら、それは動作を停止しました。私は、最新の編集で示されたコードの変更が実際に機能することを合理的に確信しています。今回はもっと注意しました。 –

関連する問題