Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/256.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 通过线程经济性实现的可伸缩性:异步操作与线程池上的多线程生产者/消费者队列?_C#_Mongodb_Asynchronous_Scalability_Threadpool - Fatal编程技术网

C# 通过线程经济性实现的可伸缩性:异步操作与线程池上的多线程生产者/消费者队列?

C# 通过线程经济性实现的可伸缩性:异步操作与线程池上的多线程生产者/消费者队列?,c#,mongodb,asynchronous,scalability,threadpool,C#,Mongodb,Asynchronous,Scalability,Threadpool,异步编程是通过线程经济性在web服务器中实现可伸缩性的一种方法,因此很少有非阻塞线程可以处理许多模拟请求。例如,Node.js使用异步操作仅使用单个线程即可实现可伸缩性 我目前正在使用数据库MongoDb,它是官方的C#驱动程序,目前还不支持异步操作。因此,我正在考虑使用一个简单的生产者/消费者队列来处理mongodb请求,以减少阻塞线程的数量。这是通过让线程池线程在队列上插入db请求,然后让它们继续执行其他任务来实现的。队列中还有一个或多个专用线程执行实际的db请求,当请求返回结果时,结果将移

异步编程是通过线程经济性在web服务器中实现可伸缩性的一种方法,因此很少有非阻塞线程可以处理许多模拟请求。例如,Node.js使用异步操作仅使用单个线程即可实现可伸缩性

我目前正在使用数据库MongoDb,它是官方的C#驱动程序,目前还不支持异步操作。因此,我正在考虑使用一个简单的生产者/消费者队列来处理mongodb请求,以减少阻塞线程的数量。这是通过让线程池线程在队列上插入db请求,然后让它们继续执行其他任务来实现的。队列中还有一个或多个专用线程执行实际的db请求,当请求返回结果时,结果将移交给线程池线程

但是,我现在想知道在使用线程池(通过TPL和c#4.0中的任务)时是否需要使用队列,因为线程池对线程数量有一个最大限制。当达到此限制时,请求将排队,直到线程池线程可用为止。听起来好像线程池提供了开箱即用的队列功能,因此使用我自己的队列什么的也得不到

我想知道的另一件事是优秀的《简而言之,C#4.0》一书中的以下评论,第页。928: “不阻止规则有一个例外。如果其他线程正在竞争同一个服务器,则在调用数据库服务器时通常可以进行阻止。这是因为在一个高度并发的系统中,数据库的设计必须使大多数查询执行得非常快。如果您最终遇到数千个并发查询,这意味着请求到达数据库的速度比数据库处理它们的速度要快。因此,线程经济是您最不担心的。”

我不明白为什么阻止一个db请求比阻止其他东西好,比如对其他服务器的请求。不阻止数据库请求不是更好吗?这样线程就可以被释放来服务可能不需要db访问的其他请求


总而言之:线程经济性可以通过依赖线程池线程的最大数量来实现吗,还是最好建立一个简单的生产者-消费者队列,为什么可以阻止对数据库服务器的调用?

在数据库查询中阻止TP线程是不可以的。引用的短语规定,只有在所有TP线程ds正在阻止这样的查询。这是无可争辩的,但这似乎是人为的

线程池管理器的主要任务是确保它运行的线程数永远不会超过机器中可用的内核数。因为这会导致线程效率低下,线程之间的上下文切换非常昂贵。但是,如果正在执行的TP线程阻塞并且没有做任何实际工作,那么这将不会很好地工作sn不够聪明,无法知道TP线程正在阻塞,也无法预测它将阻塞多长时间。只有dbase引擎会猜测它,但它不会告诉

因此,TP管理器有一个简单的算法来处理这个问题,如果没有一个正在执行的TP线程在合理的时间内完成,那么它允许另一个线程运行。您现在的活动线程比cpu内核多。“合理时间”对于.NET TP管理器是半秒

如果有必要,这种情况会继续,只要现有线程卡在车辙中,就允许运行其他线程

实际访问ThreadPool.GetMaxThreads()线程的数量非常不健康。这是一个巨大的数字,是机器中核心数量的250倍。在一台4核机器上,需要999个在499秒内没有任何进展的线程才能达到最大值。这些线程会为它们的堆栈消耗一个很酷的千兆字节地址空间。你远远超出了“这里有点不对劲”的观察,如果它到达那里的话


在这个答案中有一些简单的可量化数字。一旦一个操作开始需要半秒钟以上,那么你需要开始考虑让它在一个专用线程上执行。燃烧半秒钟只有通过阻塞才能真正实现,因此线程比TP线程更合适。是的,使用线程安全队列需要操作请求。设置挂起请求数的上限也很重要,这样就不会淹没队列。通过阻塞来限制生产者。当然,不要忘记一年后会发生什么。数据库老化时永远不会变快。

下面是一个使用线程池进行回调的队列,一个我认为这会起作用,或者有更好或更简单的替代方案吗

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);
    }
}
公共类CallbackQueueItem{
公共函数Func{get;set;}
公共操作回调{get;set;}
}
公共类回调队列{
私有只读阻止集合项目;
公共回拨队列(整数上限){
_项目=新BlockingCollection(上限);
}
私人封锁收集项目{
获取{return\u items;}
}
公开作废开始()
{
Task.Factory.StartNew(()=>{
而(!Items.IsCompleted){
CallbackQueueItem;
试一试{
item=Items.Take();
}
捕获(无效操作异常){
打破
}
如果(项!=null){
var result=item.Func();
Task.Factory.StartNew(item.Callback,result);
}
}
});
}
公共停车场(){
Items.CompleteAdding();
}
公共作废推送(CallbackQueueItem){
项目。添加(项目);
}
}
Ok t