Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/333.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#_.net_Task Parallel Library_Threadpool - Fatal编程技术网

C# 并行类如何动态调整并行级别?

C# 并行类如何动态调整并行级别?,c#,.net,task-parallel-library,threadpool,C#,.net,Task Parallel Library,Threadpool,TPL使用什么反馈来动态调整工作线程的数量 我以前的理解是,它测量任务完成率,以查看添加或删除线程是否值得。但是,为什么这段代码不断增加线程的数量,即使信号量引入了瓶颈 当然,每秒完成的任务不可能超过20个,超过2个线程也不会改善这一点 var activeThreads = 0; var semaphore = new SemaphoreSlim(2); var res = Parallel.For(0, 1_000_000, i => { Interlocked.Increme

TPL使用什么反馈来动态调整工作线程的数量

我以前的理解是,它测量任务完成率,以查看添加或删除线程是否值得。但是,为什么这段代码不断增加线程的数量,即使信号量引入了瓶颈

当然,每秒完成的任务不可能超过20个,超过2个线程也不会改善这一点

var activeThreads = 0;
var semaphore = new SemaphoreSlim(2);
var res = Parallel.For(0, 1_000_000, i =>
{
    Interlocked.Increment(ref activeThreads);
    semaphore.Wait();
    try
    {
        Thread.Sleep(100);
        Console.WriteLine("Threads: " + activeThreads);
    }
    finally
    {
        Interlocked.Decrement(ref activeThreads);
        semaphore.Release();
    }
});
我相信这正是您想要指定并行性数量的原因

 Parallel.For(0, 1000, new ParallelOptions
        {
            MaxDegreeOfParallelism = 2
        }, i => { Console.WriteLine(i); });

就我个人而言,我认为TPL库在很多情况下都能工作,但它在执行分发方面并不聪明(请原谅我的英语)。每当您在应用程序的执行过程中遇到瓶颈时,请查看管道模式。这里有一个链接可以很好地描述并行执行的不同方法:

TL;DR:您在代码中所做的事情,TPL用来证明创建一个新线程的合理性是阻塞。(同步、休眠或执行I/O都将被视为阻塞。)

一个更长的解释。。。 当您的任务运行时,它会占用其线程100毫秒(因为您
Sleep(100)
)。当您睡眠时,该线程不能用于运行其他任务,因为当睡眠时间到期时,它可能不处于可运行状态。通常我们睡觉而不是执行异步操作,因为我们需要保持调用堆栈的完整性。因此,我们依靠堆栈来维持我们的状态。堆栈是线程的独一无二的资源。(实际上,一个线程没有比它的堆栈更多的东西了。)

因此,TPL(特别是线程池)试图保持较高的占用率,但线程数较低。实现这一点的一种方法是确保系统中可运行线程的数量与虚拟处理器的数量大致相同。每次需要增加线程数时,它都必须为线程创建一个相对昂贵的堆栈,因此最好不要有这么多。不能运行的线程不能调度,因此当CPU空闲时,您需要调度一些东西来利用可用的处理资源。如果线程处于休眠状态,则无法计划其运行。因此,将向线程池中添加一个线程,并在其上安排下一个任务

当您编写这样的并行代码(如在并行for循环中)时,TPL可以对其进行分区和管理,您应该小心将线程置于不可运行状态。执行同步I/O、等待同步对象(例如信号量、事件或互斥体等)或睡眠将使线程进入一种状态,在I/O完成、睡眠间隔过期或同步对象发出信号之前,线程上无法执行任何其他操作。在此期间,线程对TPL没有好处

在您的例子中,您可以执行以下几项操作:等待信号量、睡眠,以及通过向控制台写入来执行I/O。第一件事是等待那个信号灯。如果没有发出信号,那么您马上就会遇到线程不可运行的情况,需要运行的一百万左右任务中的下一个任务必须安排在不同的线程上。如果没有,那么TPL可以证明创建一个新线程来启动更多任务是合理的。毕竟,如果是线程#987321将实际结束设置信号量以解除阻塞任务#1,该怎么办?TPL不知道您的代码是做什么的,因此出于效率的考虑,它可能会延迟创建线程一段时间,但为了正确性,最终它将不得不创建更多的线程来开始削减任务列表。有一个复杂的、特定于实现的启发式方法,它应用于监视、预测和以其他方式获得正确的效率猜测

现在,您的具体问题实际上询问了它使用什么反馈来调整线程数。正如我所说,实际的实现是复杂的,您可能应该将其视为一个黑匣子。但简而言之,如果没有可运行的线程,它可能会创建另一个线程来不断地删除任务列表(或者在这样做之前可能会等待一段时间,希望事情会释放出来),如果有太多空闲线程,它将终止空闲线程以回收它们的资源

再次重申,正如我在顶部所说的,希望这次能回答你的问题,你所做的一件事是阻止TPL创建一个新线程。。。甚至在第一个信号量上。

在2017年遇到了一个分析线程注入算法的问题。截至2019-08-01年,GitHub上的内容并未真正改变,因此文章应该仍然是最新的

有关详情:

NET线程池有两种主要的注入线程的机制: 饥饿避免机制,如果没有发现,则添加工作线程 在排队项目和爬山启发方面取得的进展 尝试在使用尽可能少的线程的同时最大化吞吐量

它根据“当前”计算所需的线程数 吞吐量,即“已完成任务的数量”(numCompletions) 在当前时间段内(采样持续时间,以秒为单位)

它还将当前线程数(currentThreadCount)计入 考虑

真正的.NET线程池只会增加一个线程数 每500毫秒线程一次。它一直这样做,直到 “线程数”已达到爬山算法允许的数量 建议

[爬山]算法仅返回符合限制的值 由ThreadPool.SetMinThreads(..)和指定 ThreadPool.SetMaxThreads(