异步编程是一种通过线程经济实现Web服务器可伸缩性的方法,因此很少有非阻塞线程可以处理许多同时发生的请求。例如,Node.js使用异步操作仅使用单个线程实现可伸缩性。
我目前正在使用数据库MongoDb,它是官方的C#驱动程序,它不支持异步操作。因此,我正在考虑使用简单的生产者/消费者队列来处理mongodb请求,以减少阻塞线程的数量。这是通过让线程池线程在队列上插入db请求然后让它们继续执行其他任务来完成的。该队列还有一个专用线程来执行实际的db请求,当请求返回结果时,结果将被移交给线程池线程。
但是,我现在想知道在使用线程池时是否需要使用队列(通过TPL和c#4.0中的任务),因为线程池对线程数有最大限制。达到此限制时,请求将排队,直到线程池线程可用。那么听起来好像线程池提供了开箱即用的队列功能,因此使用我自己的队列或者什么都不会获得什么?
我想知道的另一件事是以下评论来自优秀的“C#4.0简介”一书,页面。 928:“不阻止规则存在例外。通常可以在调用数据库服务器时阻塞 - 如果其他线程正在竞争相同的服务器。这是因为在高度并发的系统中,必须设计数据库这样大多数查询都会非常快速地执行。如果你最终得到数以千计的并发查询,这意味着请求的速度比处理它们的速度要快。因此线程经济是你最不担心的。“
我无法理解为什么在阻止其他内容(例如对其他服务器的请求)上阻止db请求。最好不要阻止数据库请求,以便释放线程以提供可能不需要数据库访问的其他请求。
总结一下:可以通过依赖最大线程池线程数来实现线程经济性,还是更好地创建一个简单的生产者消费者队列,为什么可以阻止调用数据库服务器呢? / p>
答案 0 :(得分:4)
在dbase查询中阻止TP线程是不可行的。引用的短语规定,如果所有的TP线程都阻止此类查询,那就没关系。不能与此争论,但似乎相当人为。
线程池管理器的主要工作是确保它永远不会运行更多线程而不是机器中的可用内核。因为这会使线程效率低下,所以线程之间的上下文切换非常昂贵。但是,如果执行的TP线程阻塞并且没有做任何实际工作,那么这将不会很好。 TP管理器不够聪明,知道 TP线程正在阻塞,无法预测阻塞多长时间。只有dbase引擎会对它进行猜测而且它没有说明。
因此TP管理器有一个简单的算法来处理这个问题,如果没有一个执行的TP线程在合理的时间内完成,那么它允许另一个TP线程运行。您现在拥有更多活动线程而不是cpu核心。 .NET TP管理器的“合理时间”是半秒钟。
如果有必要,这将继续,只要现有的线程卡在车辙中,就允许其他线程运行。
实际上,获取ThreadPool.GetMaxThreads()线程数是非常不健康的。这是一个庞大的数字,是机器内核数量的250倍。在4核计算机上,需要999个执行线程才能在499秒内取得任何进展才能达到最大值。这些线程将为其堆栈消耗一个很酷的千兆字节地址空间。如果它到达那里,那么方式超出了“这里有什么不对”的观察。
在这个答案中有一些容易量化的数字。一旦操作开始花费超过半秒,那么你需要开始考虑让它在专用线程上执行。只有通过阻塞才能实现燃烧半秒,因此Thread比TP线程更合适。是的,使用线程安全队列来为其提供操作请求。同样重要的是,对待处理请求的数量设置上限,这样就不会使队列泛滥。通过阻止来阻止生产者。当然,不要忘记从现在开始一年会发生什么。数据库在老化时永远不会变得更快。
答案 1 :(得分:0)
这是一个使用线程池进行回调的队列,并且建议使用队列的上限(尚未测试)。我认为这将完成这项工作,还是有更好或更简单的替代方案?
public class CallbackQueueItem<T> {
public Func<T> Func { get; set; }
public Action<object> Callback { get; set; }
}
public class CallbackQueue<T> {
private readonly BlockingCollection<CallbackQueueItem<T>> _items;
public CallbackQueue(int upperLimit) {
_items = new BlockingCollection<CallbackQueueItem<T>>(upperLimit);
}
private BlockingCollection<CallbackQueueItem<T>> Items {
get { return _items; }
}
public void Start()
{
Task.Factory.StartNew(() => {
while(!Items.IsCompleted) {
CallbackQueueItem<T> item;
try {
item = Items.Take();
}
catch(InvalidOperationException) {
break;
}
if(item != null) {
var result = item.Func();
Task.Factory.StartNew(item.Callback,result);
}
}
});
}
public void Stop() {
Items.CompleteAdding();
}
public void Push(CallbackQueueItem<T> item) {
Items.Add(item);
}
}