如何使 Stack.Pop 线程安全

发布于 2024-08-25 17:55:08 字数 1364 浏览 5 评论 0原文

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

我的问题是在(现在)线程安全堆栈上工作的线程如何知道它何时为空。即使我在 Count 周围添加另一个线程安全包装器,像 Push 和 Pop 那样锁定底层集合,我仍然会遇到访问 Count 和 Pop 不是原子的竞争条件。

我认为可能的解决方案(这是首选方案,我是否缺少任何效果更好的解决方案?):

  1. 消费者线程捕获由 Pop() 抛出的 InvalidOperationException。
  2. 当 _stack->Count == 0 时,Pop() 返回 nullptr,但是 C++-CLI 没有像 C# 那样的 default() 运算符。
  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;
};

I am using the BlockingQueue code posted in this question, but realized I needed to use a Stack instead of a Queue given how my program runs. I converted it to use a Stack and renamed the class as needed. For performance I removed locking in Push, since my producer code is single threaded.

My problem is how can thread working on the (now) thread safe Stack know when it is empty. Even if I add another thread safe wrapper around Count that locks on the underlying collection like Push and Pop do, I still run into the race condition that access Count and then Pop are not atomic.

Possible solutions as I see them (which is preferred and am I missing any that would work better?):

  1. Consumer threads catch the InvalidOperationException thrown by Pop().
  2. Pop() return a nullptr when _stack->Count == 0, however C++-CLI does not have the default() operator ala C#.
  3. Pop() returns a boolean and uses an output parameter to return the popped element.

Here is the code I am using right now:

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;
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

偏闹i 2024-09-01 17:55:08

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

这将使其行为更像框架的 ConcurrentQueue.TryDequeue(在 .NET 4 中)。


编辑:

我会这样声明:

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

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

Personally, I would use your option 3., but rename this TryPop().

This would make it behave more like the Framework's ConcurrentQueue<T>.TryDequeue (in .NET 4).


Edit:

I'd declare it like so:

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

In your implementation, you just set the T value in your method body...

痴骨ら 2024-09-01 17:55:08

选项 #3 是可行的方法,Marc Gravell 发布了 BufferedQueue/BlockingQueue 的出色实现 他称之为 SizeQueue

创建阻塞队列在 .NET 中?

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

Option #3 is the way to go and Marc Gravell has posted an excellent implementation of a BufferedQueue/BlockingQueue which he called SizeQueue:

Creating a blocking Queue<T> in .NET?

Given Marc's queue example it should be pretty easy to swap in a stack and it would work in a similar fashion.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文