多个线程等待一个事件?

发布于 2024-10-26 05:23:34 字数 944 浏览 1 评论 0原文

(我认为)我想要的是多个线程可以等待的 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 code> 可以由另一个线程调用,并且所有等待线程将立即检测到更改 - 重要的是,没有自旋锁或 Threading.Sleeps。

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

What (I think) I want is the equivelant of an AutoResetEvent that multiple threads can wait on, all to be resumed when it's set.

I know this can be achieved by having one AutoResetEvent per thread and setting each of them - but is there an easier way? A way that doesn't depend on arrays of eventhandles?

Effectively what (I think) I'd like is to be able to do this:

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
}

ie, any number of threads can call WaitForBlob, and at any time (no race conditions) SetBlob can be called by yet another thread, and all waiting threads will detect the change immediately - and importantly, with no spin locks or Threading.Sleeps.

Now I think I can implement a "MultiEventHandle" relatively easily. But my question is... is there a better way? Surely I'm going about this wrong as it must be a pretty common use case, but I can't seem to find an in-built tool for the job. I'm afraid I may be going about inventing a square wheel here..

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

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

发布评论

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

评论(3

梨涡少年 2024-11-02 05:23:34

我在幕后使用 Monitor.PulseAll/Wait 将可能的解决方案包装到“WatchedVariable”类中(在此过程中了解了一些有关 Monitor 类的信息)。在此发布以防其他人遇到同样的问题 - 可能对不可变数据结构有一定用处。感谢乔恩·斯基特的帮助。

用法:

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

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

现在咨询元来找出我应该接受哪个答案..

I've wrapped up a possible solution into a "WatchedVariable" class using Monitor.PulseAll/Wait behind the scenes (learning a bit about the Monitor class in the process). Posting here in case anyone else ever runs into the same problem - may be of some use with immutable data structures. Thanks to Jon Skeet for assistance.

Usage:

private WatchedVariable<string> state;

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

Implementation:

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

Whilst it's passed my test cases, use at your own risk.

Now to consult meta to figure out which answer I'm supposed to accept..

ゝ杯具 2024-11-02 05:23:34

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

当然,这意味着如果您需要在所有等待线程完成后重置事件,则需要某种方法来检测它。您可以可能使用信号量来代替,但我怀疑它会很复杂。

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

Any reason not to use a ManualResetEvent? That won't reset itself when one waiting thread has passed, so they'll all be released.

Of course, it means that if you need to Reset the event after all the waiting threads have gone through, you'd need some way of detecting that. You could possibly use a Semaphore instead, but I suspect it would be complicated.

Do you need to reset the event immediatley after you've set it, in your case?

断念 2024-11-02 05:23:34

我想出了一个不同的解决方案来解决这个问题。
它假设等待事件的线程在 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 循环之前释放所有线程。
如果有人能解释一下那就太好了。

I came up with a different solution to this problem.
It assumes the threads waiting for the event are not tight loops on the WaitOne() calls, and there's some work between the wait calls.
It uses one AutoResetEvent, calls WaitOne(0) and Set() continuously until no other threads waits for the event.

// 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();

The above code is tested for 1000 threads, it did released them all (though there's an overhead of too many iteration on the while loop, which can be easily limited to zero when there's a bit more work between the wait calls in the threads.

Something that is not clear to me from the documentation is whether it is possible for Set() to release a WaitOne() that was called later on the same thread - if this situation is possible then this solution is not safe to use since it might not release all the threads before exiting the while loop.
It would be nice if someone could shed some light on it.

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