有没有办法在C#中确定等待锁的线程数量?

6

我正在使用C#中的简单锁定,使用lock语句。有没有办法确定有多少其他线程正在等待在对象上获取锁定?我基本上想限制等待锁定的线程数量为5。如果第六个线程需要获取锁定,则我的代码将抛出异常。


6
这听起来像是一个繁琐解决方案的下半部分,你真正想要解决的实际问题是什么? - Anon.
很难解释,但我有一个对象,它是一个第三方Web服务的包装器。技巧在于对第三方服务的调用必须同步,因此需要锁定包装器对象。我在网站环境中使用包装器对象,因此多个线程可能同时尝试使用它。我想限制等待线程队列为5的原因是我稍微担心会有人轻易地创建DoS攻击。第三方Web服务非常慢,因此有人可以轻松发送请求,我将有数百个阻塞的线程。 - Ben Mills
1
@Ben:听起来你需要限制的资源是系统中的线程数,而不是等待锁的数量。永远不要为每个请求创建一个线程;应该为每个处理器创建一个线程,如果处理器用尽了,就让客户端等待直到有线程空闲。 - Eric Lippert
@Eric:我不确定,但是ASP.NET是否每个请求使用1个线程?也许我错了,阻塞ASP.NET线程会停止我的网站服务更多的请求,直到该线程不再被锁定。那将是很糟糕的。 - Ben Mills
@Eric:我对多线程不是很了解,但是应该每个处理器或每个核心制作一个线程,还是这取决于要解决的问题? - Joan Venge
@Ben:ASP从线程池中提取线程,该线程池限制每个处理器分配的线程数。您需要平衡一些线程无法充分利用处理器的事实和线程切换是昂贵的事实。有关详细信息,请参见http://www.codeproject.com/Articles/38501/Multi-Threading-in-ASP-NET.aspx。 - Eric Lippert
4个回答

7

这可以通过Semaphore类轻松实现,它将为您进行计数。请注意,在下面的代码中,我使用信号量对等待资源的线程数量进行非阻塞检查,然后我使用普通的lock来序列化访问该资源。如果有超过5个线程等待资源,则会抛出异常。

public class YourResourceExecutor
{
  private Semaphore m_Semaphore = new Semaphore(5, 5);

  public void Execute()
  {
    bool acquired = false;
    try
    {
      acquired = m_Semaphore.WaitOne(0);
      if (!acquired)
      {
        throw new InvalidOperationException();
      }
      lock (m_Semaphore)
      {
        // Use the resource here.
      }
    }
    finally
    {
      if (acquired) m_Semaphore.Release();
    }
  }
}

有一个显著的变体模式。您可以将方法名称更改为TryExecute,并使其返回bool而不是抛出异常。这完全取决于您。

请记住,在锁表达式中使用的对象不是锁的主题。它仅用作代码同步块的标识符。使用相同对象获取锁的任何代码块都将有效地被序列化。被“锁定”的是代码块,而不是在lock表达式中使用的对象。


我认为我理解了这个问题,但是当使用lock()锁定m_Semaphore时,其他线程是否可以获取信号量锁?我在想是否应该将lock()放在一个单独的对象上。 - Ben Mills
@Ben:是的,你绝对可以为“锁”使用一个单独的对象。我只是选择使用已经存在的对象,而不是创建一个单独的对象。在Semaphore上使用“锁”不会以任何方式影响Semaphore的行为。 - Brian Gideon
@Brian,我是新手,但我认为如果您通过lock()获取了一个对象上的锁,则其他线程无法以任何方式访问该对象。 - Ben Mills
@Ben:不,lock表达式中使用的对象并不是锁的主体。它仅仅作为代码同步块的标识符。任何使用相同对象获取锁的代码块将被有效地串行化。被“锁定”的是代码块,而不是lock表达式中使用的对象。 - Brian Gideon
@Brian:太好了,谢谢你的回答。这正是我要找的。不使用SemaphoreSlim有什么原因吗? - Ben Mills
@Ben:SemaphoreSlim 可以完美地工作……实际上可能更好。 - Brian Gideon

2

锁定语句是 Monitor.EnterMonitor.Exit 的快捷方式。我认为,您没有机会获得等待对象的数量。


1

您可以使用一个简单的共享计数器(整数),在锁定语句之前递增。如果该值等于5,则使您的线程避免锁定语句。然而,挑战在于您需要锁定计数器以确保递增操作是原子的。


你可以使用 Interlocked.Increment 来实现。 - Conrad Frix
+1 给Conrad的建议;可能是实现这个最简单的方法。 - KeithS

1
不,lock()使用Monitor类,该类没有成员用于查找排队线程的数量。
您可以指定超时时间。
而且,当队列填满时抛出异常似乎不是一个好主意。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接