Как сделать Stack.Pop потокобезопасным - PullRequest
1 голос
/ 15 марта 2010

Я использую код BlockingQueue, отправленный в этом вопросе , но понял, что мне нужно использовать стек вместо очереди, учитывая, как работает моя программа. Я преобразовал его в стек и переименовал класс по мере необходимости. Для производительности я снял блокировку в Push, так как код моего производителя однопоточный.

Моя проблема в том, как поток, работающий с (теперь) безопасным стеком, может знать, когда он пуст. Даже если я добавлю другую потокобезопасную оболочку вокруг Count, которая блокирует базовую коллекцию, как это делают Push и Pop, я все равно столкнусь с условием гонки, при котором доступ к Count и затем Pop не атомарны.

Возможные решения, как я их вижу (что является предпочтительным, и я пропускаю какие-либо, которые будут работать лучше?):

  1. Потоки-получатели ловят исключение InvalidOperationException, создаваемое Pop ().
  2. Pop () возвращает nullptr, когда _stack-> Count == 0, однако C ++ - CLI не имеет оператора default (), как a C #.
  3. 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;
};

Ответы [ 2 ]

4 голосов
/ 15 марта 2010

Лично я бы использовал ваш вариант 3., но переименуйте этот TryPop ().

Это заставит его вести себя больше как ConcurrentQueue<T>.TryDequeue платформы (в .NET 4).


Edit:

Я бы сказал так:

public:
bool TryPop([Out] T% result);

В вашей реализации вы просто устанавливаете значение T в теле вашего метода ...

2 голосов
/ 15 марта 2010

Вариант № 3 - путь, и Марк Гравелл опубликовал отличную реализацию BufferedQueue/BlockingQueue, которую он назвал SizeQueue:

Создание очереди блокировки в .NET?

Учитывая пример очереди Марка, это должно быть довольно легко поменять местами в стеке, и это будет работать аналогичным образом.

...