如何使Stack.Pop线程安全

时间:2010-03-15 16:58:23

标签: .net multithreading c++-cli stack thread-safety

我正在使用发布的in this question BlockingQueue代码,但我意识到我需要使用堆栈而不是队列,因为我的程序运行方式。我将其转换为使用Stack并根据需要重命名该类。为了性能,我删除了Push中的锁定,因为我的生产者代码是单线程的。

我的问题是如何在(现在)线程安全堆栈上运行线程,知道什么时候它是空的。即使我在Count周围添加另一个线程安全包装器来锁定底层集合,如Push和Pop do,我仍然遇到访问Count然后Pop不是原子的竞争条件。

我看到它们的可能解决方案(这是首选的,我错过任何可以更好的解决方案吗?):

  1. 使用者线程捕获Pop()抛出的InvalidOperationException。
  2. 当_stack-> Count == 0时,Pop()返回一个nullptr,但是C ++-CLI没有默认的()运算符ala C#。
  3. Pop()返回一个布尔值,并使用输出参数返回弹出元素。
  4. 以下是我现在使用的代码:

    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 个答案:

答案 0 :(得分:4)

就个人而言,我会使用您的选项3.,但重命名此TryPop()。

这会使它的行为更像Framework ConcurrentQueue<T>.TryDequeue(在.NET 4中)。


编辑:

我会这样声明:

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

在您的实现中,您只需在方法体中设置T值...

答案 1 :(得分:2)

选项#3是可行的方式,Marc Gravell发布了BufferedQueue/BlockingQueue的优秀实施,他称之为SizeQueue

Creating a blocking Queue<T> in .NET?

鉴于Marc的队列示例,在堆栈中进行交换应该非常容易,并且它可以以类似的方式工作。