可以将当前计数减少N(N> = 1)的信号量?

时间:2019-05-23 21:24:14

标签: c# multithreading thread-synchronization

我正在实现一个流量控制组件,该组件限制了可以发送的最大请求数。每个工作线程都可以发送单个请求或一批请求,但是在任何时候未决请求的总数不应超过最大数目。

我最初想使用SemaphoreSlim实现: 将信号量初始化为最大请求数,然后当工作线程将要调用服务时,它必须获取足够的令牌数,但是实际上我发现SemaphoreSlim和Semaphore仅允许线程将信号量数减少1。希望通过工作线程携带的请求数量来减少计数。

我应该在这里使用什么同步原语?

只需说明一下,该服务支持批处理,因此一个线程可以在一个服务调用中发送N个请求,但是相应地,它应该能够将信号量的当前计数减少N。

3 个答案:

答案 0 :(得分:1)

可以使用Monitor原语来实现具有公平(无饥饿)的解决方案,

public sealed class Guard
{
    public Guard(int max)
    {
        Max = max;
    }

    private int current;
    private readonly object mutex = new object();
    private readonly object queueLock = new object();

    public int Max { get; }

    private int ticket;
    private int position;

    public IDisposable Enter(int count)
    {
        if (count > Max) throw new Exception("Max number of units exceeded");

        lock (queueLock)
        {
            var t = ticket; // take a ticket
            ticket++;

            while (t > position) Monitor.Wait(queueLock); // wait in line
        }

        lock (mutex)
        {
            while (current + count > Max) Monitor.Wait(mutex);
            current += count;
        }

        lock (queueLock)
        {
            position++; // move the queue forward
            Monitor.Pulse(queueLock);
        }

        return new Anonymous(() => Exit(count));
    }

    private sealed class Anonymous : IDisposable
    {
        private readonly Action dispose;

        public Anonymous(Action dispose)
        {
            this.dispose = dispose;
        }

        public void Dispose()
        {
            dispose();
        }
    }

    private void Exit(int count)
    {
        lock (mutex)
        {
            current -= count;
            Monitor.Pulse(mutex);
        }
    }
}

用法:

using (guard.Enter(3))
{
    // Do something
}

答案 1 :(得分:0)

在我看来,您想要类似的东西

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个)可能永远无法获得足够的资源。

答案 2 :(得分:0)

使用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功能的信号灯,则修改Stephen Toub的AsyncSemaphore应该很简单。