I was looking over the HH source for the work queue and noticed this comment in Win32AddEntry: "TODO(casey): Switch to InterlockedCompareExchange eventually so that any thread can add?"
I'm interested in creating a multiple producer/consumer queue so thought I'd try it out, but quickly realised that it's quite a tricky problem to solve. Given the code...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | internal void Win32AddEntry(platform_work_queue *Queue, platform_work_queue_callback *Callback, void *Data) { // TODO(casey): Switch to InterlockedCompareExchange eventually // so that any thread can add? uint32 NewNextEntryToWrite = (Queue->NextEntryToWrite + 1) % ArrayCount(Queue->Entries); Assert(NewNextEntryToWrite != Queue->NextEntryToRead); platform_work_queue_entry *Entry = Queue->Entries + Queue->NextEntryToWrite; Entry->Callback = Callback; Entry->Data = Data; ++Queue->CompletionGoal; _WriteBarrier(); Queue->NextEntryToWrite = NewNextEntryToWrite; ReleaseSemaphore(Queue->SemaphoreHandle, 1, 0); } |
We can't use an ICE on NextEntryToWrite to see if we should add the task, because it has to be the last thing written otherwise a consumer might pick up a task that hasn't been written yet as soon as NextEntryToWrite gets updated by the ICE. It seems to me that we need to atomically set the task entry and NextEntryToWrite all at the same time to keep the queue in a valid state.
I was just wondering if anyone had any insights on this?