创建太多线程会发生什么?是否会导致CPU崩溃或Windows操作系统上存在某种内部负载平衡机制?
我正在运行以下代码:
private async void A(string[] a)
{
var tasks = a.Select(B);
await Task.WhenAll(tasks);
}
private async Task B(string b)
{
new Thread(async delegate ()
{
//all the work that needs to be done
}).Start();
}
我正在运行一系列异步任务但在每个异步方法中我已经封装了需要在新线程中完成的所有工作。如果我多次拨打B,会发生什么?处理器如何处理太多线程?
首先,为什么要在Tasks中运行Threads?在99.9%的情况下它只是没有意义。在剩余的0.1%中,maaybe有点意义,但你很可能应该使用TaskCompletionSource而不是Task。
设计任务是为了让您可以拥有将这些任务排队的调度程序,监视这些任务何时休眠/等待/等等,并在此期间重用线程来运行其他任务。
基本上,您将“工作”包装到任务中,然后将这些任务提供给调度程序,然后调度程序决定运行以执行这些任务的时间,时间和数量。
调度员并不神奇,他们没有水晶球来预测未来。我说他们“决定”,但这只是半真的:调度程序通常遵循一些通用规则,具体取决于它的种类。所以,你为你的幻想选择了正确的调度程序并完成了。
说真的,放弃当前的方法。请改用调度程序。您甚至可以拥有一个调度程序,它将在单独的线程上执行每个任务。它将等同于您当前的方法。但是,您将能够快速切换到另一个调度程序并感受到差异。
这里有几个资源,一个非常重要的库:
认真。如果您不想阅读/ etc,那么只需阅读第一篇文章并阅读不同调度程序的名称,以便至少了解您选择忽略的可能性。
最后,回答这个问题,是的,Windows有点负载均衡。它会尝试防止运行太多线程。它实际上会在给定的时间点运行少量线程(或多或少等于处理器中逻辑执行单元的数量),其余部分将休眠并等待其时间。 Windows会偶尔在它们之间切换,所以你会发现它们好像都在运行,但有些速度较慢,有些速度更快。
但是,这并不意味着您可以创建无限量的线程。显然,有一个内存限制:如果你有X GB的内存,你不能保留比你可以放入内存更多的内存。我现在开玩笑了,但由于有一些明显的限制,会有更多的限制。但是,这里有一点严肃,因为,你看,每个线程都有一个堆栈,那个堆栈可以按兆字节排序,所以如果你有32位处理器,堆栈的数量最多可以达到几千个。所以..是的,记忆可以是一个限制。它在64位上不太明显,但是,当然,你没有足够的RAM来填充整个64位地址空间,所以在64位你也有一个限制。
由于Windows会尝试保留所有线程的记录,即使是那些正在运行的线程,也会浪费时间跟踪这些记录。此外,它会浪费时间进行切换,因为作为操作系统,它会尝试让它们全部切换和运行。它直接意味着你创建的线程越多(1/10/100/1000 / ..)一切都会运行得更慢 - 而且比分割N线程更慢(不是:1 / 0.1 / 0.01 / 0.001 / ..,但是:1 / 0.1 / 0.097 / 0.0089 / ..)因为保留记录和切换时浪费了时间。
线程也有优先权。内部系统线程通常具有更高的优先级系统会更频繁地切换到它们,这意味着您运行的线程越多,您的应用程序处理的速度就越慢。
还有一个硬限制。为了跟踪重要对象,Windows使用“句柄”的概念。每个窗口,每个线程,每个共享内存块,每个打开的文件流等,只要它还活着(并且有点长) - 具有唯一的句柄。你可以通过使用所有句柄实际上STARVE窗口。
例如,如果您用完所有GUI句柄,则无法打开新窗口。或窗口区域。或控制。想象一下,打开一个记事本,它启动并显示没有菜单,也没有显示TextArea,因为没有足够的空闲句柄来分配它们。
由于该限制,Windows实际上限制了每个进程的已分配句柄数。这意味着,例如,Windows有一个1M句柄池,但每个进程最多只能使用1K。这些数字是人为的,只是让你有所了解。
由于物理(本机)线程必须有一个句柄,这是另一个限制。
我不是真正的专家,让我们回到专家撰写的一系列文章,他们隐藏了线程限制,处理限制等等:
CPU只执行操作系统告诉它的操作系统,操作系统是in charge,其中线程运行以及它们在中断之前运行了多长时间。调度程序中内置了一些反饥饿,所以它永远不应该完全锁定系统,但是如果你只是继续产生尽可能多的线程,直到你的内存或地址空间不足,那么你几乎可能会瘫痪。
如果我们假装您的程序是唯一运行的程序,那么如果任务是CPU限制的话,理想的线程数与CPU核心数相同。如果任务受I / O限制或需要等待内核对象,那么更多线程可能是理想的。
如果您创建了数千个线程,那么您将浪费时间间隔在它们之间切换,您的工作将需要更长的时间才能完成。您应该使用thread pool来执行您的工作,而不是手动启动新线程,因此Windows本身可以平衡最佳线程数。
await
和其他高级异步关键字可能已经使用了线程池。
线程确实有很大的成本 - 非常粗略 - 想象每个线程100K字节(它们每个都需要一个堆栈),并且它们每个都在操作系统组件(例如调度程序)上放置一些必须管理它们的轻微负担。
线程DO提供了一个非常简单的模型来管理异步任务。我是这种方法的忠实粉丝。
但是如果你打算使用很多线程,请考虑使用线程池作为重用底层线程对象的方法(同时有很多可运行的 - 只是没有运行)。
而且 - 由于您使用的是C#,异步任务(qazxsw poi)是一种更有效的策略。
通常情况下 - 实施的简单性比效率更重要(达到一定程度)。您使用线程池描述的内容(以节省实际线程数)可能正常工作。