Есть ли способ ожидания на нескольких семафорах - PullRequest
4 голосов
/ 06 мая 2009

Я пытаюсь написать приложение, которое может ожидать несколько пулов ресурсов одновременно. Каждый пул ресурсов контролируется 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);
        }
    }
}

Ответы [ 3 ]

6 голосов
/ 06 мая 2009

Конечно, вы можете использовать его, но он будет срабатывать только в том случае, если все семафоры запущены одновременно. В зависимости от того, как структурирована остальная часть вашего приложения, действительно могут быть проблемы с голоданием. Например, если у вас есть два ресурса, 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. Резьба 3 блока на A.
  3. (проходит время)
  4. Тема 1 выпускает A.
  5. Резьба 3, замки А, блоки на Б.
  6. Резьба 1 блоков на A.
  7. (проходит время)
  8. Тема 2 выпускает Б.
  9. Thread 3 блокирует B, работает, затем разблокирует.
  10. Поток 1 блокирует A, делает успехи.

Как вы можете видеть, было некоторое время простоя, когда поток 1 был заблокирован на A, хотя не было никакой реальной работы, которую нужно было сделать. Тем не менее, благодаря этому мы значительно повысили шансы потока 3 на успехи.

Будет ли это хороший компромисс, будет зависеть от вашего приложения - если вы можете однозначно сказать, что несколько потоков никогда не входят в блокировку, это может даже не иметь значения. Но нет единственно верного пути:)

2 голосов
/ 06 мая 2009

Каково ваше намерение?

  1. Вам нужно подождать, пока ВСЕ семафоры не будут сигнализированы? Если это так, см. Предыдущие сообщения.

  2. Вам нужно подождать, пока какой-либо из семафоров не станет сигнальным? Если это так, используйте WaitAny () вместо этого.

Примечания: WaitAny принимает массив дескрипторов ожидания и возвращает индекс дескриптора, который стал сигнализировать.

Если сигнализируется более одного дескриптора, WaitAny возвращает первый

0 голосов
/ 06 мая 2009

Цель WaitAll - избежать тупиковых ситуаций, связанных с приобретением только тех объектов, которые вы ожидаете. WaitAll будет успешным, если и только если доступны все семафоры, которые вы пытаетесь использовать. Если они не доступны, он не заблокирует ни одного из них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...