有没有办法等待多个信号量

Ori*_*ian 4 c# multithreading

我正在尝试编写一个可以同时等待多个资源池的应用程序.每个资源池由a控制Semaphor.我可以使用WaitHandle.WaitAll()我传递给整个信号列表的地方吗?此实现是否存在潜在的死锁问题?

我目前的实施:

namespace XXX
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;

    public class ResourcePoolManager
    {
        private readonly IDictionary<string, Semaphore> resourcePools = new Dictionary<string, Semaphore>();

        public void AddResourcePool(string resourceName, int maxConcurrentConsumers)
        {
            this.resourcePools.Add(resourceName, new Semaphore(maxConcurrentConsumers, maxConcurrentConsumers));
        }

        public void RequestResource(string resourceName)
        {
            this.resourcePools[resourceName].WaitOne();
        }

        public void RequestMultipleResources(string[] resourceNames)
        {
            Semaphore[] resources = resourceNames.Select(s => this.resourcePools[s]).ToArray();

            WaitHandle.WaitAll(resources);
        }

        public void ReleaseResource(string resourceName)
        {
            this.resourcePools[resourceName].Release(1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

bdo*_*lan 6

当然,你可以使用它,但它只会在所有信号量被立即触发时触发.根据应用程序其余部分的结构,可能确实存在饥饿问题.例如,如果您有两个资源,A和B,以及三个线程:

  1. 持续获取资源A,使用它一秒钟,然后释放它并循环
  2. 持续获取资源B,使用它一秒钟,然后释放它并循环
  3. 等待A和B都可用

您可以轻松地等待A和B同时可用.

根据您的应用程序,简单地按顺序取每个信号量可能会更好,这可以避免这种饥饿问题,但会引入传统的死锁问题.但是,如果您确定这些锁在大多数情况下都可用,那么它可能是安全的(但也可能是等待您的应用程序在实际负载下的定时炸弹......)

给定您的示例代码,另一种选择是在信号量上创建全局排序 - 比如按名称排序 - 并始终确保按顺序获取它们.如果这样做,只需通过按升序逐个锁定每个信号量即可执行多重锁定.

在这种情况下,释放的顺序并不严格 - 但是如果你发布了无序,你应该在你刚刚获得的锁之后释放所有锁'之后再获得更多(这是一个经验法则,应该给你死锁安全.有可能通过详细分析进一步放松.推荐的方法是尽可能以相反的顺序发布,在这种情况下,您可以在任何时候进行进一步的采集.例如:

  1. 获得锁A
  2. 获得锁B
  3. 获取锁C
  4. 释放锁C.
  5. 获得锁D
  6. 版本B(现在在你发布D之前不会获得任何东西!)
  7. 发布D.
  8. 获得E
  9. 发布E.
  10. 发布A.

只要一切都遵循这些规则,就不可能出现死锁,因为服务员的周期无法形成.

这种方法的缺点是它可能会在等待另一个线程时通过持有锁来延迟其他线程.这不会永远持续下去; 在上面三个线程的例子中,我们可能有这样的场景,例如:

  1. 在开始时,线程2保持B.线程1保持A.
  2. A上的线程3块
  3. (时间流逝)
  4. 线程1发布A.
  5. 线程3锁定A上的B,块.
  6. A上的线程1块
  7. (时间流逝)
  8. 线程2发布B.
  9. 线程3锁定B,确实有效,然后解锁.
  10. 线程1锁定A,取得进展.

正如您所看到的,尽管没有真正的工作要做,但是线程1在A上被阻止了一些停机时间.但是,通过这样做,我们大大提高了线程3取得进展的机会.

这是否是一个很好的权衡将取决于您的应用程序 - 如果您可以明确地说多个线程永远不会进入锁定,它甚至可能不重要.但没有一个正确的方法:)