【问题标题】:Multiple threads waiting on one event?多个线程等待一个事件?
【发布时间】:2011-07-21 22:29:45
【问题描述】:

(我认为)我想要的是 AutoResetEvent 的等价物,多个线程可以等待,所有线程都可以在设置时恢复。

我知道这可以通过为每个线程设置一个 AutoResetEvent 并设置每个线程来实现 - 但有没有更简单的方法?一种不依赖于事件句柄数组的方式?

实际上(我认为)我希望能够做到这一点:

private volatile string state;
private MultiEventHandle stateChanged = new MultiEventHandle();

public void WaitForBlob()
{
  while (true)
  {
    object saved = stateChanged.Current;  // some sentinel value
    if (state == "Blob") break;
    stateChanged.WaitTilNot(saved);  // wait til sentinel value != "current"
  }
}

public void SetBlob()
{
  state = "Blob";
  stateChanged.Change();  // stateChanged.Current becomes a new sentinel object
}

即,任意数量的线程都可以调用WaitForBlob,并且在任何时候(没有竞争条件)SetBlob 可以被另一个线程调用,并且所有等待的线程将立即检测到更改 - 重要的是,没有自旋锁或 Threading.Sleeps。

现在我想我可以相对容易地实现“MultiEventHandle”。但我的问题是......有没有更好的方法?我肯定会解决这个问题,因为它一定是一个非常常见的用例,但我似乎无法找到适合这项工作的内置工具。恐怕我要在这里发明一个方轮了..

【问题讨论】:

    标签: c# multithreading synchronization


    【解决方案1】:

    我已经在幕后使用 Monitor.PulseAll/Wait 将一个可能的解决方案封装到“WatchedVariable”类中(在此过程中了解了一点 Monitor 类)。在这里发帖以防其他人遇到同样的问题 - 可能对不可变数据结构有一些用处。感谢 Jon Skeet 的帮助。

    用法:

    private WatchedVariable<string> state;
    
    public void WaitForBlob()
    {
      string value = state.Value;
      while (value != "Blob")
      {
        value = state.WaitForChange(value);
      }
    }
    

    实施:

    public class WatchedVariable<T>
        where T : class
    {
        private volatile T value;
        private object valueLock = new object();
    
        public T Value
        {
            get { return value; }
            set
            {
                lock (valueLock)
                {
                    this.value = value;
                    Monitor.PulseAll(valueLock);  // all waiting threads will resume once we release valueLock
                }
            }
        }
    
        public T WaitForChange(T fromValue)
        {
            lock (valueLock)
            {
                while (true)
                {
                    T nextValue = value;
                    if (nextValue != fromValue) return nextValue;  // no race condition here: PulseAll can only be reached once we hit Wait()
                    Monitor.Wait(valueLock);  // wait for a changed pulse
                }
            }
        }
    
        public WatchedVariable(T initValue)
        {
            value = initValue;
        }
    }
    

    虽然它通过了我的测试用例,但使用风险自负。

    现在咨询 meta 以确定我应该接受哪个答案..

    【讨论】:

      【解决方案2】:

      有什么理由不使用ManualResetEvent?当一个等待线程过去时,它不会自行重置,因此它们都会被释放。

      当然,这意味着如果您需要在所有等待线程都经过之后Reset 事件,您需要某种方法来检测它。您可以可能改用Semaphore,但我怀疑它会很复杂。

      在您的情况下,您是否需要在设置后立即重置事件?

      【讨论】:

      • 在实际用例中,我有几个不同的函数会阻塞,等待状态达到他们想要的。即 WaitForBlob 和 WaitForSusan 等。我想要的是,每当全局状态发生变化时,所有等待的线程都会重新评估它们的条件,看看它们现在是否可以开始了。我还没有完全弄清楚如何弯曲 ManualResetEvents 来做到这一点。
      • 有关更多信息,它是由于涉足 Eric Lippert 在 C# 中的不变性而产生的。我有一个不可变的 AVLTree 存储一堆数据。有时它的一部分会被“锁定”,因为我等待从设备中读取它。现在,如果我想检索这个“锁定”段,它必须等待响应才能返回最新值。所以我需要做的是等待树被改变,如果那部分仍然被锁定,继续等待直到它不是。多个线程需要能够干净地执行此操作,最好不要旋转/小睡眠。
      • @Mania:如果你有使用 AutoResetEvent 的代码,ManualResetEvent 是完全相同的——它只是在第一个线程设法等待时不会自行重置。您可能会考虑的另一个选项是使用事件,其中每个事件处理程序都会对相应线程已调用 Wait on 的监视器进行 Pulse。话虽如此,在某些方面使用像这样的低级概念听起来是个坏主意……您使用的是 .NET 4 吗?如果是这样,TPL 中的某些内容可能会对您有所帮助。
      • 可悲的是,3.5。我已经找到了一种可以使用 ManualResetEvents 进行处理的方法,但它不是很漂亮。目前正在阅读 Monitor.Pulse/Wait,但正如您所说,它们似乎有点太低级了。我想我将使用 ManualResetEvents 制作建议的“MultiEventHandle”类来隐藏逻辑。感谢您的帮助。
      【解决方案3】:

      我想出了一个不同的解决方案来解决这个问题。 它假定等待事件的线程在WaitOne() 调用上不是紧密循环,并且在等待调用之间有一些工作。 它使用一个AutoResetEvent,连续调用WaitOne(0)Set(),直到没有其他线程等待事件。

      // the only event we'll use:
      AutoResetEvent are = new AutoResetEvent(false);
      // starting threads:
      for (int i = 0; i < 10; i++)
      {
          string name = "T" + i; 
          new Thread(() => { while (true) { are.WaitOne(); WriteLine(name); } }).Start();
      }
      
      // release all threads and continue:
      while (!are.WaitOne(0))
          are.Set();
      

      上面的代码针对 1000 个线程进行了测试,它确实释放了所有线程(尽管在 while 循环上存在过多迭代的开销,当在线程。

      文档中我不清楚的一点是 Set() 是否可以释放稍后在同一线程上调用的 WaitOne() - 如果这种情况是可能的,那么这个解决方案使用起来不安全因为它可能不会在退出 while 循环之前释放所有线程。 如果有人能对此有所了解,那就太好了。

      【讨论】:

        猜你喜欢
        • 2020-10-04
        • 1970-01-01
        • 2015-04-24
        • 1970-01-01
        • 1970-01-01
        • 2016-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多