我正在实现一个流量控制组件,该组件限制了可以发送的最大请求数。每个工作线程都可以发送单个请求或一批请求,但是在任何时候未决请求的总数不应超过最大数目。
我最初想使用SemaphoreSlim实现: 将信号量初始化为最大请求数,然后当工作线程将要调用服务时,它必须获取足够的令牌数,但是实际上我发现SemaphoreSlim和Semaphore仅允许线程将信号量数减少1。希望通过工作线程携带的请求数量来减少计数。
我应该在这里使用什么同步原语?
只需说明一下,该服务支持批处理,因此一个线程可以在一个服务调用中发送N个请求,但是相应地,它应该能够将信号量的当前计数减少N。
答案 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
应该很简单。