私はこれをテストしていません(コンパイルを確認することは別として)が、これでうまくいくと思います。確かに、私が最初に思ったよりも少しトリッキーでした。いくつかの明らかな最適化を行うことができることに注意してください。わかりやすくするため、また必要になる可能性のあるデバッグを支援するために、最適化されていない形式のままにしておきます。エラーチェックも省略しました。
#include <intrin.h>
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
__declspec(align(4)) volatile LONG thread_state = 2;
// 0 (00): sleeping
// 1 (01): sleeping, wake request pending
// 2 (10): awake, no additional wake request received
// 3 (11): awake, at least one additional wake request
void wake_thread(void)
{
LONG old_state;
old_state = _InterlockedOr(&thread_state, 1);
if (old_state == 0)
{
// This is the first wake request since the consumer thread
// went to sleep. Set the event.
SetEvent(hEvent);
return;
}
if (old_state == 1)
{
// The consumer thread is already in the process of being woken up.
// Any items added to the queue by this thread will be processed,
// so we don't need to do anything.
return;
}
if (old_state == 2)
{
// This is an additional wake request when the consumer thread
// is already awake. We've already changed the state accordingly,
// so we don't need to do anything else.
return;
}
if (old_state == 3)
{
// The consumer thread is already awake, and already has an
// additional wake request registered, so we don't need to do
// anything.
return;
}
BigTrouble();
}
void sleep_thread(void)
{
LONG old_state;
// Debugging only, remove this test in production code.
// The event should never be signaled at this point.
if (WaitForSingleObject(hEvent, 0) != WAIT_TIMEOUT)
{
BigTrouble();
}
old_state = _InterlockedAnd(&thread_state, 1);
if (old_state == 2)
{
// We've changed the state from "awake" to "asleep".
// Go to sleep.
WaitForSingleObject(hEvent, INFINITE);
// We've been buzzed; change the state to "awake"
// and then reset the event.
if (_InterlockedExchange(&thread_state, 2) != 1)
{
BigTrouble();
}
ResetEvent(hEvent);
return;
}
if (old_state == 3)
{
// We've changed the state from "awake with additional
// wake request" to "waking". Change it to "awake"
// and then carry on.
if (_InterlockedExchange(&thread_state, 2) != 1)
{
BigTrouble();
}
return;
}
BigTrouble();
}
基本的に、これは手動リセットイベントと2ビットフラグを使用して、自動リセットイベントの動作を再現します。状態図を描くと、より明確になる場合があります。スレッドセーフは、どの関数がどの遷移を実行できるかに関するルールと、イベントオブジェクトのシグナリングがいつ許可されるかによって異なります。
社説として:同期コードをwake_thread関数とsleep_thread関数に分離しているため、少し厄介だと思います。同期コードがキューの実装に移動された場合は、おそらくより自然で、わずかに効率的で、ほぼ確実に明確になります。