能否通过 C# 线程同步以原子方式发出信号并等待?

发布于 2024-11-09 15:12:04 字数 390 浏览 3 评论 0原文

我在 C# 中的线程同步方面遇到一些问题。我有一个由两个线程操纵的共享对象,我已经使用 lock() 互斥地访问了该对象,但我也想根据共享对象的状态来阻止每个线程。特别是当对象为空时阻塞线程 A,当对象已满时阻塞线程 B,并在对象状态更改时让另一个线程向被阻塞的线程发出信号。

我尝试使用 ManualResetEvent 执行此操作,但遇到了竞争条件,其中线程 B 将检测到对象已满,移至 WaitOne,线程 A 将进入并清空对象(每次访问时向 MRE 发出信号,并阻止自身一次)在线程 A 到达其 WaitOne 之前,对象为空),这意味着线程 A 正在等待线程未满,即使它尚未满。

我想如果我可以调用像“SignalAndWaitOne”这样的函数,它会在等待之前自动发出信号,这会阻止这种竞争条件吗?

谢谢!

I'm having some issues with thread synchronization in C#. I have a shared object which gets manipulated by two threads, I've made access to the object mutually exclusive using lock(), but I also want to block each thread depending on the state of the shared object. Specially block thread A when the object is empty, block thread B when the object is full, and have the other thread signal the blocked thread when the object state changes.

I tried doing this with a ManualResetEvent, but have run into a race condition where thread B will detect the object is full, move to WaitOne, and thread A will come in and empty the object (signalling the MRE every access, and block itself once the object is empty) before thread A hits its WaitOne, meaning thread A is waiting for the thread to not be full, even though it isn't.

I figure that if I could call a function like 'SignalAndWaitOne', that would atomically signal before waiting, it would prevent that race condition?

Thanks!

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

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

发布评论

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

评论(2

记忆で 2024-11-16 15:12:04

执行此操作的典型方法是使用 Monitor.Enter、Monitor.Wait 和 Monitor.Pulse 来控制对共享队列的访问。草图:

shared object sync = new object()
shared Queue q = new Queue()

Producer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsFull)
            Wait(sync)     
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not full.
        q.Enqueue(something)
        Pulse(sync)
        // This puts the waiting consumer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock

Consumer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsEmpty)
            Wait(sync)
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not empty.
        q.Dequeue()
        Pulse(sync)
        // This puts the waiting producer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock

A typical way to do this is to use Monitor.Enter, Monitor.Wait and Monitor.Pulse to control access to the shared queue. A sketch:

shared object sync = new object()
shared Queue q = new Queue()

Producer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsFull)
            Wait(sync)     
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not full.
        q.Enqueue(something)
        Pulse(sync)
        // This puts the waiting consumer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock

Consumer()
    Enter(sync)
    // This blocks until the lock is acquired
    while(true)
        while(q.IsEmpty)
            Wait(sync)
            // this releases the lock and blocks the thread 
            // until the lock is acquired again

        // We have the lock and the queue is not empty.
        q.Dequeue()
        Pulse(sync)
        // This puts the waiting producer thread to the head of the list of 
        // threads to be woken up when this thread releases the lock
回忆躺在深渊里 2024-11-16 15:12:04

.NET 已提供 BlockingCollection 4.0。

如果您使用的是早期版本,则可以使用 < code>直接监视类。

编辑:以下代码完全未经测试,并且不处理较小的 maxCount 值 (<= 2)。它也没有任何超时或取消的规定:

public sealed class BlockingList<T>
{
  private readonly List<T> data;
  private readonly int maxCount;

  public BlockingList(int maxCount)
  {
    this.data = new List<T>();
    this.maxCount = maxCount;
  }

  public void Add(T item)
  {
    lock (data)
    {
      // Wait until the collection is not full.
      while (data.Count == maxCount)
        Monitor.Wait(data);

      // Add our item.
      data.Add(item);

      // If the collection is no longer empty, signal waiting threads.
      if (data.Count == 1)
        Monitor.PulseAll(data);
    }
  }

  public T Remove()
  {
    lock (data)
    {
      // Wait until the collection is not empty.
      while (data.Count == 0)
        Monitor.Wait(data);

      // Remove our item.
      T ret = data.RemoveAt(data.Count - 1);

      // If the collection is no longer full, signal waiting threads.
      if (data.Count == maxCount - 1)
        Monitor.PulseAll(data);
    }
  }
}

A BlockingCollection is already provided by .NET 4.0.

If you're on an earlier version, then you can use the Monitor class directly.

EDIT: The following code is totally untested, and does not handle maxCount values that are small (<= 2). It also doesn't have any provisions for timeouts or cancellation:

public sealed class BlockingList<T>
{
  private readonly List<T> data;
  private readonly int maxCount;

  public BlockingList(int maxCount)
  {
    this.data = new List<T>();
    this.maxCount = maxCount;
  }

  public void Add(T item)
  {
    lock (data)
    {
      // Wait until the collection is not full.
      while (data.Count == maxCount)
        Monitor.Wait(data);

      // Add our item.
      data.Add(item);

      // If the collection is no longer empty, signal waiting threads.
      if (data.Count == 1)
        Monitor.PulseAll(data);
    }
  }

  public T Remove()
  {
    lock (data)
    {
      // Wait until the collection is not empty.
      while (data.Count == 0)
        Monitor.Wait(data);

      // Remove our item.
      T ret = data.RemoveAt(data.Count - 1);

      // If the collection is no longer full, signal waiting threads.
      if (data.Count == maxCount - 1)
        Monitor.PulseAll(data);
    }
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文