多个线程等待一个事件?
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 循环之前释放所有线程。 如果有人能对此有所了解,那就太好了。
【讨论】:
以上是关于多个线程等待一个事件?的主要内容,如果未能解决你的问题,请参考以下文章
事件同步-——CreateEvent( )事件对象实现线程同步