使用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接收到信号。">

所以我只是担心,这实际上不会允许我的网络应用程序在等待时继续使用这个被阻止的线程,这会使这个练习的全部意义变得毫无意义。

WaitOne确实阻塞了线程,并且该线程将停止在CPU核心上调度,直到发出信号量为止。但是,您持有线程池中的大量线程的时间可能很长("long"表示"long than ~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();
}
});
}

最新更新