Я использую код BlockingQueue, отправленный в этом вопросе , но понял, что мне нужно использовать стек вместо очереди, учитывая, как работает моя программа. Я преобразовал его в стек и переименовал класс по мере необходимости. Для производительности я снял блокировку в Push, так как код моего производителя однопоточный.
Моя проблема в том, как поток, работающий с (теперь) безопасным стеком, может знать, когда он пуст. Даже если я добавлю другую потокобезопасную оболочку вокруг Count, которая блокирует базовую коллекцию, как это делают Push и Pop, я все равно столкнусь с условием гонки, при котором доступ к Count и затем Pop не атомарны.
Возможные решения, как я их вижу (что является предпочтительным, и я пропускаю какие-либо, которые будут работать лучше?):
- Потоки-получатели ловят исключение InvalidOperationException, создаваемое Pop ().
- Pop () возвращает nullptr, когда _stack-> Count == 0, однако C ++ - CLI не имеет оператора default (), как a C #.
- Pop () возвращает логическое значение и использует выходной параметр для возврата вытолкнутого элемента.
Вот код, который я сейчас использую:
generic <typename T>
public ref class ThreadSafeStack
{
public:
ThreadSafeStack()
{
_stack = gcnew Collections::Generic::Stack<T>();
}
public:
void Push(T element)
{
_stack->Push(element);
}
T Pop(void)
{
System::Threading::Monitor::Enter(_stack);
try {
return _stack->Pop();
}
finally {
System::Threading::Monitor::Exit(_stack);
}
}
public:
property int Count {
int get(void)
{
System::Threading::Monitor::Enter(_stack);
try {
return _stack->Count;
}
finally {
System::Threading::Monitor::Exit(_stack);
}
}
}
private:
Collections::Generic::Stack<T> ^_stack;
};