使用信号量调用WaitOne会释放调用线程来执行其他工作吗?

时间:2018-10-30 07:07:18

标签: c# asp.net-mvc multithreading semaphore waitone

我的应用程序需要每分钟在每个租户上执行许多任务。这些是一劳永逸的操作,所以我不想使用Parallel.ForEach来处理此问题。

相反,我正在遍历租户列表,并触发ThreadPool.QueueUserWorkItem处理每个租户任务。

foreach (Tenant tenant in tenants)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessTenant), tenantAccount);
}

此代码在生产中可以完美运行,通常可以在5秒钟内处理100多个租户。

但是,在应用程序启动时,这会导致100%的CPU使用率,而诸如EF之类的东西会在启动过程中预热。为了限制这一点,我实现了如下信号灯:

private static Semaphore _threadLimiter = new Semaphore(4, 4);

这个想法是将任务处理限制为只能使用一半的计算机逻辑处理器。在ProcessTenant方法内部,我调用:

try
{
    _threadLimiter.WaitOne();

    // Perform all minute-to-minute tasks
}
finally
{
    _threadLimiter.Release();
}

在测试中,这似乎完全符合预期。启动时的CPU使用率保持在50%左右,并且似乎不影响初始启动的速度。

所以问题主要是关于调用WaitOne时实际发生的事情。这是否释放线程以处理其他任务-与异步调用类似? MSDN文档指出,WaitOne:“阻塞当前线程,直到当前的WaitHandle收到信号为止。”

因此,我只是在警惕,这实际上不会允许我的Web应用程序在等待时继续使用此阻塞线程,这会使整个练习的意义变得毫无意义。

1 个答案:

答案 0 :(得分:1)

WaitOne确实会阻塞线程,并且该线程将停止在CPU内核上进行调度,直到发出信号量为止。但是,您可能会长时间从线程池中保留大量线程(“ long”(如“长于〜500 ms”)。这可能是一个问题,因为线程池的增长非常缓慢,因此您可能会阻止应用程序的其他部分正确使用它。

如果您打算等待大量时间,则可以使用自己的线程:

foreach (Tenant tenant in tenants)
{
    new Thread(ProcessTenant).Start(tenantAccount);    
}

但是,您仍然在内存中为每个项目保留一个线程。尽管他们因为在信号量上睡觉而不会吃掉CPU,但他们仍然没有使用RAM(每个线程大约1MB)。取而代之的是,让单个专用线程等待信号量,并根据需要排队新项目:

// Run this on a dedicated thread
foreach (Tenant tenant in tenants)
{
    _threadLimiter.WaitOne();
    ThreadPool.QueueUserWorkItem(_ => 
    {
        try         
        {
            ProcessTenant(tenantAccount);
        }
        finally
        {
            _threadLimiter.Release();
        }
    });
}