多个线程等待一个事件?

Posted

技术标签:

【中文标题】多个线程等待一个事件?【英文标题】: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”。但我的问题是......有没有更好的方法?我肯定会解决这个问题,因为它一定是一个非常常见的用例,但我似乎无法找到适合这项工作的内置工具。恐怕我要在这里发明一个方轮了..

【问题讨论】:

【参考方案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 循环之前释放所有线程。 如果有人能对此有所了解,那就太好了。

【讨论】:

以上是关于多个线程等待一个事件?的主要内容,如果未能解决你的问题,请参考以下文章

Java并发:等待事件发生后所有线程继续执行

事件同步-——CreateEvent( )事件对象实现线程同步

等待多个并发事件完成的模型

C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)

事件对象

主程序如何通过等待而不是加入同时等待多个线程?