Синхронизация потоков: общие ресурсы и действия с разным количеством требуемых ресурсов

У меня есть следующая задача:

Существует пул эквивалентных ресурсов (например, входных слотов) и набор действий, которые необходимо выполнить с этим пулом.

Каждое действие выполняется в отдельном потоке.

Для каждого действия требуется разное количество доступных входных слотов.

Я хочу использовать что-то вроде семафоров для следующей задачи:

Все потоки действий выполняются одновременно

Действие выполняется, когда необходимое количество входных слотов свободно для использования.

Слоты освобождаются, когда действие завершено

Я не могу использовать обычный семафор, потому что невозможно установить отрицательное начальное значение семафора или уменьшить значение до тех пор, пока оно не станет отрицательным.

Поэтому мне нужен «семафор» с функцией WaitMultiple (int N), которая делает что-то вроде «подождать, пока значение ресурса не достигнет N»

Любые предложения, как я могу это решить? Я предпочитаю блокировку/освобождение потока, я не могу позволить себе активные циклы


person HtonS    schedule 19.12.2011    source источник
comment
Я не работал с семафорами С#, поэтому не могу ответить. Однако мне интересно, как вы пришли к выводу, что использование цикла в отдельном потоке для диспетчеризации слишком дорого?   -  person Fdr    schedule 19.12.2011
comment
под активным циклом я имел в виду что-то вроде for(;;) if(free_slots › N) break; и что ты имел в виду?   -  person HtonS    schedule 19.12.2011
comment
+1: Это потрясающий вопрос.   -  person Brian Gideon    schedule 19.12.2011


Ответы (2)


Я не уверен, что семафоры здесь полезны.

Я думаю, вы можете сделать это с помощью пула потоков и некоторого кода в защищенном разделе CS/mutex. Поместите действия в коллекцию (список/очередь/стек, что лучше всего подходит для вашего алгоритма диспетчеризации) и инициализируйте количество слотов. Затем и всякий раз, когда слоты освобождаются в результате выполнения действий, войдите в CS и решите, какие действия выполнять с учетом текущего количества слотов. Отправьте эти действия в пул потоков, соответствующим образом уменьшите количество слотов и выйдите из CS.

"Slot-count" может быть простым целым числом или количеством коллекций какого-то сложного класса - на самом деле это не имеет значения.

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

Я не вижу необходимости в отдельном потоке диспетчера и, конечно же, в циклах процессора! Я боюсь, что любое решение, основанное на потоках, пытающихся накопить цель слота, ожидая на каком-либо синхронизирующем объекте доступных слотов, будет сопряжено с риском взаимоблокировки - многие потоки могут застрять с частичным подсчетом слотов и, таким образом, предотвратить продвижение вперед. Кроме того, вы не сможете настроить алгоритм, чтобы, скажем, предотвратить голодание действий с высокими требованиями к слоту.

person Martin James    schedule 19.12.2011
comment
+1: мне нравится эта идея. Я предполагаю какую-то контрмеру, чтобы действия, требующие большого количества слотов, не вытеснялись действиями, требующими всего несколько слотов. Но, да, это кажется действительно масштабируемой стратегией. - person Brian Gideon; 19.12.2011
comment
Одной из возможностей может быть упорядочение списка действий по некоторому «приоритету» на основе требований к слоту и времени ожидания. Возвращенные слоты будут применяться к элементам в начале списка. В конце концов, действие, которому требуется большое количество слотов, окажется в начале списка, потому что оно ждало целую вечность, и поэтому оно будет сидеть там, пока не получит свои слоты. - person Martin James; 19.12.2011
comment
Спасибо, Мартин! Ваш ответ помог! - person HtonS; 20.12.2011

Вы можете реализовать свой собственный быстрый семафор с возможностью отрицательного счета. Например, создайте такой класс MySemaphore:

public class MySemaphore
{
    private int size; // number of occupied resources
    private readonly int capacity; // total capacity

    public MySemaphore(int size, int capacity)
    {
        this.size = size;
        this.capacity = capacity;
    }

    public void Lock(int count) // acquire "count" resources
    {
        lock(this)
        {
            while(capacity - size < count)
            {
                Monitor.Wait(this);
            }
            size += count;
        }
    }        

    public void Unlock(int count) // release "count" resources
    {
        lock(this)
        {
            size -= count;
            Monitor.PulseAll(this);
        }
    }
}
person Tudor    schedule 19.12.2011
comment
Спасибо, Тудор. Думаю, я совмещу ваш ответ с предыдущим. Жаль, что я не могу проверить два сообщения в качестве ответа - person HtonS; 20.12.2011