【问题标题】:WaitHandle.WaitAny and Semaphore classWaitHandle.WaitAny 和信号量类
【发布时间】:2009-08-19 13:51:38
【问题描述】:

编辑:我想为自己提出这个问题表示暂时的精神错乱,但当时这是有道理的(见下面的编辑 2)。

对于 .NET 3.5 项目,我需要检查两种资源(R1R2)的可用性。每个资源类型在任何时候都可以有(比如说)10 个实例。

当任一类型的资源可用时,我的工作线程需要唤醒(线程数量可变)。在较早的实现中,只有一种资源类型,我使用信号量来检查可用性。

现在我需要等待两个单独的信号量(S1S2)来跟踪资源的可用性。

WaitHandle[] waitHandles = new WaitHandle[] { s1, s2 };
int signalledHandle = WaitHandle.WaitAny(waitHandles);

switch (signalledHandle)
{
    case 0:
        // Do stuff
        s1.Release();
    case 1:
        // Do stuff
        s2.Release();
}

然而,这有一个问题。来自WaitAny 上的 MSDN 文档:

如果不止一个对象变成 在通话期间发出信号,返回 value 是数组的索引 具有最小信号的对象 所有信号的索引值 对象。

这表明在调用 WaitAny 后,我可能将两个信号量计数都减少了 1。因为signalledHandle 将表明s1 已发出信号,所以我将开始使用资源R1,并在完成后释放它。但是,由于我不知道是否已发出 S2 信号,因此该资源的可用性计数现在可能已关闭。如果这种情况发生 10 次,我的信号量将永久“空”,并且资源 R2 将不再使用。

解决这个问题的最佳方法是什么?我应该从使用两个信号量切换到简单的计数器和 AutoResetEvent 以在任一计数器更改时发出信号吗?我错过了一些更优雅的方法吗?

编辑 1:
根据 Ravadre 的说法,在WaitAny 之后,实际上只有一个信号量会被更改。稍微修改他的例子似乎证实了这一点,但是有没有人可以指出我的一些官方文档指定这一点?

编辑 2:
我在回家的路上一直在思考这个问题。直到那时我才意识到WaitAny 必须是这样才能有任何用处。这个问题不仅限于信号量,而是几乎任何类型的同步对象,使WaitAny 实际上毫无用处。

【问题讨论】:

  • 我添加了一个有价值的(虽然不是官方的)来源,您可能想要查看。

标签: c# .net multithreading waithandle


【解决方案1】:

如果我正确理解了您的问题,我认为您的解决方案完全可以,而您只是过度解释了 msdn 报价。调用WaitHandle.WaitAny() 时,您将获得最低索引,但您将只锁定一个waitHandle(本例中为信号量),请查看以下示例代码:


Semaphore s1 = new Semaphore(1, 2);
Semaphore s2 = new Semaphore(1, 2);

WaitHandle[] handles = new WaitHandle[] { s1, s2 };

int x = WaitHandle.WaitAny(handles);

int prevS1 = s1.Release();
int prevS2 = s2.Release();

在这种情况下,prevS1 将等于 0,因为信号量 s1“已等待”,所以它的计数器已减少到 0,而 prevS2 将等于 1,因为它的状态没有'因为它是实例化而没有改变(Release() 方法在释放之前返回计数器,所以返回 1 意味着“它是 1,现在它是 2”)。

您可能想查看的另一个资源:http://www.albahari.com/threading/part2.aspx#_Wait_Handles。虽然它不是“官方”来源,但我认为没有理由认为它不可靠。

【讨论】:

  • 在本例中,您将无条件释放两个信号量。来自 MSDN 的另一句话:“确保线程不会多次释放信号量是程序员的责任。”。如果您执行此WaitAny/Release 序列两次,s2.Release() 将引发异常:将给定计数添加到信号量将导致其超过其最大计数。
  • 是的,但这是一个示例,这就是为什么我将信号量实例化为具有最大计数器 == 2,所以我知道我可以释放它们一次,而不必担心异常,我是什么这里证明的是,即使两个信号量都被释放(计数器 > 0)并且 WaitAny() 返回 0(来自您的报价 - 最小索引),只有第一个信号量被锁定,而另一个信号量保持不变。
  • 同样的修改似乎也支持你的说法,即只有一个信号量会被改变(我个人觉得 locked 在信号量上下文中有点混乱),但我想看看一些官方规范表明这是有保证的。
  • Locked 也不适合我,虽然我找不到更好的替代品(altered 的缺点是它没有说明它是被释放还是“锁定”)。至于保证 - MSDN 声称,WaitAny() 返回“满足等待的对象的数组索引。”,这从理论上表明我们正在谈论一个对象,而且只有一个。参考文献中没有任何地方提到改变多个对象的状态。
  • 我认为 Ravadre 是对的。据我了解,试图传达的事实是,在WaitAny 返回后,获取返回索引的Semaphore。因此,即使同时释放了另一个Semaphore,也不会被获取,因此不需要释放。当然,我不确定这一点,也没有可以向您指出的文档。但是,它似乎应该像这样工作。
【解决方案2】:

出于您的目的,当调用WaitHandle.WaitAny() 方法时,结果并不重要。重要的是发出了一个 WaitHandle 信号,因此您需要再次尝试获取锁/同步。

void Main() {
 var semaphoreOne = new SemaphoreSlim(0, 1);
 var semaphoreTwo = new SemaphoreSlim(0, 1);

 ReleaseSemaphoreAfterWhile(semaphoreOne);

 bool firstAccepted;
 bool secondAccepted = false;
 while ((firstAccepted = semaphoreOne.Wait(0)) == false &&
  (secondAccepted = semaphoreTwo.Wait(0)) == false) {
  var waitHandles = new [] {
   semaphoreOne.AvailableWaitHandle, semaphoreTwo.AvailableWaitHandle
  };
  WaitHandle.WaitAny(waitHandles);
  Console.WriteLine("SemaphoreOne Before Lock = " + semaphoreOne.CurrentCount);
  Console.WriteLine("SemaphoreTwo Before Lock = " + semaphoreTwo.CurrentCount);
 }

 if (firstAccepted) {
  Console.WriteLine("semaphore 1 was locked");
 } else if (secondAccepted) {
  Console.WriteLine("semaphore 2 was locked");
 } else {
  throw new InvalidOperationException("no semaphores were signaled");
 }
}

Random rd = new Random();
public void ReleaseSemaphoreAfterWhile(SemaphoreSlim semaphore) {
var sleepWork =(int)rd.Next(100, 1000);
 ThreadPool.QueueUserWorkItem(t => {
  Thread.Sleep(10000 + sleepWork);
  semaphore.Release();
 });
}

还有其他实现相同想法/逻辑的空间,但是以这种方式使用 while 循环可以保证只有一个信号量会被获取,如果没有空间,它会锁定线程直到任何 WaitHandle收到信号 - 考虑 SemaphoreSlim 实例 .Release() 方法。

不幸的是(正如 cmets 所指出的那样)他们对网络中的线程同步存在一些误解,但上面的代码应该可以帮助您解决问题。

【讨论】:

猜你喜欢
  • 2020-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2011-04-20
  • 2011-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多