2010-02-20 45 views
4

我打算设计一个类的功能的执行限制在给定的量在规定时间内,例如:编码/设计一个通用的线程安全的限制(即限制X()的每秒Ÿ多次执行)

  • 过程最大值。在1秒内有5个文件

它应该是线程安全的并且性能命中应该是最小的。

你会如何设计这样的课程?我有几个想法,但没有一个对我来说似乎是对的。

是否有这样的任务任何已知的设计模式? (我编码.NET但任何语言是OK

从外面类应该像这样工作(假设它是单):

设置:

Limiter.Instance.MaxExecutionPerSecond = 5 

然后,我们将执行我们的函数之前的线程中调用这个,如果它需要它会阻塞线程:

Limiter.Instance.WaitIfRequired() 
+0

了解CCR? (http://msdn.microsoft.com/en-us/library/bb648752.aspx) – 2010-02-20 13:21:45

回答

2

像这样的事情?

using Timer = System.Threading.Timer; 

class Limiter{ 
    public static readonly Limiter Instance = new Limiter(); 

    Limiter(){} 

    int   max; 
    Semaphore counter; 
    List<Timer> timers = new List<Timer>(); 

    // warning: not thread safe! 
    public int MaxExecutionPerSecond{ 
     get{return max;} 
     set{counter = new Semaphore(max = value, value);} 
    } 

    public void WaitIfRequired(){ 
     // Our semaphore starts with a count of MaxExecutionPerSecond. 
     // When we call WaitOne(), it decrements the count. If the count 
     // is already zero, the call to WaitOne() will block until another 
     // thread calls Release() to increment the count. 
     counter.WaitOne(); 

     // Set a timer to increment the semaphore in one second. 
     Timer t = null; 
     t = new Timer(o=>{ 
      // Increment the semaphore. 
      counter.Release(); 

      // We no longer need to protect this timer from the GC. 
      timers.Remove(t); 
      t.Dispose(); 
     }); 

     // Hold a reference to this timer to keep it from being disposed of. 
     timers.Add(t); 

     // Set the timer to release the semaphore in one second. 
     t.Change(1000, Timeout.Infinite); 
    } 
} 

编辑

有一点要记住的是,上面的代码只能防止多个线程开始一次。如果线程长时间运行,那么一次运行的许多线程仍然是可能的。例如,如果你每秒启动5个线程,但每个线程运行1秒,那么在2秒后的任何给定时间,理论上你将有10个线程在运行。

如果你想确保你永远不会有超过5个线程同时运行,最简单的事情就是免去自定义类Limiter,直接使用Semaphore

const int maxThreadCount = 5; 
static Semaphore counter = new Semaphore(maxThreadCount, maxThreadCount); 

static void NewThread(object state){ 
    counter.WaitOne(); 

    // do something 

    counter.Release(); 
} 

现在,这只是简单的,因为它可以。但有一点需要注意:创建新线程并立即让它们睡觉通常被认为是一个坏主意。这使用系统资源来创建一个没有任何作用的线程。最好排队请求并启动新线程(或者更好的是,使用线程池线程)来处理它们,直到它们有资格运行。这是更复杂和更难以正确。根据设计的不同,它可能需要额外的调度程序/管理线程。事情是这样的:

class Limiter{ 
    class WorkData{ 
     readonly ParameterizedThreadStart action; 
     readonly object     data; 

     public ParameterizedThreadStart Action{get{return action;}} 
     public object     Data {get{return data;}} 

     public WorkData(ParameterizedThreadStart action, object data){ 
      this.action = action; 
      this.data = data; 
     } 
    } 

    readonly Semaphore  threadCount; 
    readonly Queue<WorkData> workQueue = new Queue<WorkData>(); 
    readonly Semaphore  queueCount = new Semaphore(0, int.MaxValue); 

    public Limiter(int maxThreadCount){ 
     threadCount = new Semaphore(maxThreadCount, maxThreadCount); 
     Thread t = new Thread(StartWorkItems); 
     t.IsBackground = true; 
     t.Start(); 
    } 

    void StartWorkItems(object ignored){ 
     while(queueCount.WaitOne() && threadCount.WaitOne()){ 
      WorkData wd; 
      lock(workQueue) 
       wd = workQueue.Dequeue(); 

      ThreadPool.QueueUserWorkItem(DoWork, wd); 
     } 
    } 
    void DoWork(object state){ 
     WorkData wd = (WorkData)state; 
     wd.Action(wd.Data); 
     counter.Release(); 
    } 

    public void QueueWork(ParameterizedThreadStart action, object data){ 
     lock(workQueue) 
      workQueue.Enqueue(new WorkData(action, data)); 
     queueCount.Release(); 
    } 
} 

在这个类中,我已经去除了单属性和给定的构造函数maxThreadCount参数。这样可以避免第一类资产缺乏线程安全性。还有其他方法可以添加线程安全性,但这是最简单的。

+0

真的很聪明的实现,谢谢。关于创建成本的任何想法让我们说每秒100个定时器? – 2010-02-20 16:59:11

+0

我期望的成本可以忽略不计。信号量和定时器都不是特别昂贵。当然,直接剖析这段代码有点困难,但作为一个简单的测试,我将'MaxExecutionPerSecond'设置为100,并调用了'WaitIfRequired()'10,000次。正如预期的那样,总执行时间约为99秒,任务管理器显示进程的CPU时间为0秒。 – 2010-02-20 20:38:26

+0

我真的很喜欢没有Thread.Sleep()的方式 – 2010-02-20 20:43:25

0

我认为,获取当前时间相对于该功能所做的任何事情都是便宜的。

在这种情况下,我做了Scala中这样的事情,和设计模式会去是这样的:

  • 如果已经执行WaitIfRequired应该阻止。 (这将是Java中的“同步方法”;我不确定.NET的等价物是什么。)
  • 保持一个调用WaitIfRequired的时间队列。
  • 如果队列长度超过MaxExecutionPerSecond,则将其缩回,直到长度不超过MaxExecutionPerSecond
  • 弹出排队顶部,如果是MaxExecutionPerSecond长。如果现在的时间超过一秒钟,则将当前时间推到队列的尾部并立即返回;足够的时间已经过去了。如果它少于一秒钟,则在推动和返回之前,休息一段时间以使其成为第二秒(即,经过1秒的时间)。

就是这样。

您可以要求不超过N多玩这个在时间T由T.

更换“一秒”

呼吁现在,如果你有同步的方法,事情变得有点更有趣(但只是一点点)。然后您需要一个外部锁定机制来确保一次只有一个线程正在读取和等待。

1

对于你正在寻找的分辨率,DateTime.Now是一个很好的时钟,非常便宜。它以15毫秒以上的精度进行更新。下面是一个例子,调用运行()方法在执行操作之前:

using System; 

class Throttle { 
    private int mTrigger; 
    private int mOperations; 
    private DateTime mStart; 

    public Throttle(int maxOperationsPerSecond) { 
    if (maxOperationsPerSecond < 1) throw new ArgumentException(); 
    mTrigger = maxOperationsPerSecond; 
    } 
    public void Operation() { 
    mOperations += 1; 
    if (mOperations > mTrigger) { 
     TimeSpan span = DateTime.UtcNow - mStart; 
     if (span.TotalMilliseconds < 1000) 
     System.Threading.Thread.Sleep(1000 - (int)span.TotalMilliseconds); 
     mOperations = 1; 
    } 
    if (mOperations == 1) mStart = DateTime.UtcNow; 
    } 
} 

创建你的线程类的实例,不分享。

+0

非常好的一个,它不是线程安全的,但很容易添加。 – 2010-02-20 18:57:36

+0

顺便说一句你为什么说不分享它?如果我不分享它,没有目的,因为一个线程只能执行一个操作。这个想法是从一个点扼杀多个线程操作。 – 2010-02-20 18:58:58

+0

我以为你一次只想调节一个线程。如果你想分享它,你只需要在Operation()方法的开头添加lock语句。 – 2010-02-20 19:41:23