6

编辑:即使问这个问题,我也想为暂时的精神错乱辩护,但当时这是有道理的(见下面的编辑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();
}

然而,这有一个问题。从 MSDN 文档中WaitAny

如果在调用期间有多个对象被发出信号,则返回值是所有发出信号对象中索引值最小的发出信号对象的数组索引。

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

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

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

编辑2:
我在回家的路上正在考虑这个问题。直到那时我才意识到,这必须是真的,WaitAny才有用。这个问题不仅限于信号量,而是几乎任何类型的同步对象,WaitAny几乎没有用处。

4

2 回答 2

5

如果我正确理解了您的问题,我认为您的解决方案完全可以,而您只是过度解释了 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 。虽然它不是“官方”来源,但我认为没有理由认为它不可靠。

于 2009-08-19T14:22:48.470 回答
0

出于您的目的,在调用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()方法。

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

于 2017-08-25T01:48:41.360 回答