Семафор, который может уменьшить его текущий счет на N (N> = 1)? - PullRequest
2 голосов
/ 24 мая 2019

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

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

Какой примитив синхронизации я должен использовать здесь?

Просто для пояснения, служба поддерживает пакетную обработку, поэтому один поток может отправить N запросов за один вызов службы, но, соответственно, он должен иметь возможность уменьшить текущее число семафоров на N.

Ответы [ 2 ]

0 голосов
/ 24 мая 2019

Относительно легко создать пользовательский семафор с функцией уменьшения CurrentCount более чем на единицу, используя мощные Wait and Pulse методы Monitor класса.

public class Semaphore2
{
    public int CurrentCount { get; private set; }
    private readonly object _locker = new object();

    public Semaphore2(int initialCount)
    {
        CurrentCount = initialCount;
    }

    public void Wait(int count)
    {
        lock (_locker)
        {
            while (CurrentCount < count)
            {
                Monitor.Wait(_locker);
            }
            CurrentCount -= count;
        }
    }

    public void Release(int count)
    {
        lock (_locker)
        {
            CurrentCount += count;
            Monitor.PulseAll(_locker);
        }
    }
}

Если вам нужен семафор с функциональностью WaitAsync, модифицировать Стивена Туба AsyncSemaphore.

довольно просто.
0 голосов
/ 24 мая 2019

Мне кажется, что вы хотите что-то вроде

using System;
using System.Collections.Generic;
using System.Threading;

namespace Sema
{
    class Program
    {
        // do a little bit of timing magic
        static ManualResetEvent go = new ManualResetEvent(false);

        static void Main()
        {
            // limit the resources
            var s = new SemaphoreSlim(30, 30);

            // start up some threads
            var threads = new List<Thread>();
            for (int i = 0; i < 20; i++)
            {
                var start = new ParameterizedThreadStart(dowork);
                Thread t = new Thread(start);
                threads.Add(t);
                t.Start(s);
            }

            go.Set();

            // Wait until all threads finished
            foreach (Thread thread in threads)
            {
                thread.Join();
            }
            Console.WriteLine();
        }

        private static void dowork(object obj)
        {
            go.WaitOne();
            var s = (SemaphoreSlim) obj;
            var batchsize = 3;

            // acquire tokens
            for (int i = 0; i < batchsize; i++)
            {
                s.Wait();
            }

            // send the request
            Console.WriteLine("Working on a batch of size " + batchsize);
            Thread.Sleep(200);

            s.Release(batchsize);
        }
    }
}

Однако вы скоро поймете, что это вызывает тупики. Кроме того, вам потребуется некоторая синхронизация с семафором, чтобы гарантировать, что один поток либо получит все свои токены, либо ни одного.

        var trylater = true;
        while (trylater)
        {
            lock (s)
            {
                if (s.CurrentCount >= batchsize)
                {
                    for (int i = 0; i < batchsize; i++)
                    {
                        s.Wait();
                    }

                    trylater = false;
                }
            }

            if (trylater)
            {
                Thread.Sleep(20);
            }
        }

Теперь это потенциально страдает от голода. Огромная партия (скажем, 29) может никогда не получить достаточно ресурсов, пока выполняется сотни отдельных запросов.

...