如果你开始太多线程会发生什么?

时间:2017-05-24 23:23:49

标签: c# windows multithreading processor

创建太多线程会发生什么?它会导致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,会发生什么?处理器如何处理太多线程?

3 个答案:

答案 0 :(得分:4)

CPU只执行操作系统告诉它的操作系统,操作系统运行的线程为in charge,以及它们在中断之前运行多长时间。调度程序中内置了一些反饥饿,所以它永远不应该完全锁定系统,但是如果你只是继续产生尽可能多的线程,直到你的内存或地址空间不足,那么你几乎可能会瘫痪。

如果我们假装您的程序是唯一运行的程序,那么如果任务受CPU限制,则理想线程数与CPU核心数相同。如果任务受I / O限制或需要等待内核对象,那么更多线程可能是理想的。

如果您创建了数千个线程,那么您将浪费时间间隔在它们之间切换,您的工作将需要更长时间才能完成。您应该使用thread pool来执行工作,而不是手动启动新线程,因此Windows本身可以平衡最佳线程数。

await和其他高级异步关键字可能已经使用了线程池。

答案 1 :(得分:1)

首先,为什么要在Tasks中运行Threads?在99.9%的情况下它只是没有意义。在剩下的0.1%中,maaybe有点意义,但你很可能应该使用TaskCompletionSource而不是Task。

任务的设计使你可以拥有调度程序,这些调度程序将对这些任务进行排队,监视这些任务何时休眠/等待/等等,并在此期间重用线程来运行其他任务。

基本上,你包装你的工作"进入任务,然后将这些任务提供给调度程序,然后调度程序决定运行以执行这些任务的时间,时间和数量。

调度员并不神奇,他们没有水晶球来预测未来。我说他们决定",但这只是半真的:调度程序通常遵循一些通用规则,具体取决于它的类型。所以,你为你的幻想选择了正确的调度程序。

说真的,放弃当前的做法。请改用调度程序。您甚至可以拥有一个调度程序,它将在单独的线程上执行每个任务。它将等同于您当前的方法。但是,您将能够快速切换到另一个调度程序并感受到差异。

这里有一些资源供您使用,这是一个非常重要的图书馆:

严重。如果您不想阅读/ etc,那么只需阅读第一篇文章并阅读不同调度程序的名称,以便至少了解您选择忽略多少种可能性。

最后,回答这个问题,是的,Windows有点负载均衡。它会尝试防止运行太多线程。它实际上会在给定的时间点运行少量线程(或多或少等于处理器中逻辑执行单元的数量),其余部分将休眠并等待其时间。 Windows会偶尔在它们之间切换,所以你会发现好像所有这些都在运行,但有些速度较慢,其中一些更快。

但是,这并不意味着您可以创建无限量的线程。显然,存在内存限制:如果你有10 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使用" handle"的概念。每个窗口,每个线程,每个共享内存块,每个打开的文件流等,只要它还活着(并且有点长) - 具有唯一的句柄。实际上,您可以通过使用所有句柄来 STARVE 窗口。

例如,如果您用完所有GUI句柄,则无法打开新窗口。或窗口区域。或控制。想象一下,打开一个记事本,它启动并显示没有菜单,也没有显示TextArea,因为没有足够的空闲句柄来分配它们。

由于该限制,Windows实际上限制了每个进程分配的句柄数。这意味着,例如,Windows有一个1M句柄池,但每个进程最多只能使用1K。这些数字是人为的,只是让你有了一个想法。

由于物理(本机)线程必须有句柄,这是另一个限制。

我不是这方面真正的专家,让我们回到专家撰写的一系列文章中,他们隐藏了线程限制,处理限制等等:

https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/

答案 2 :(得分:0)

线程确实有很高的成本-非常粗略-想象每个线程100K字节(它们每个都需要一个堆栈来完成一件事情),并且它们每个都对必须管理它们的操作系统组件(例如调度程序)施加了些负担。

线程确实提供了一个用于管理异步任务的非常简单的模型。我是这种方法的忠实拥护者。

但是,如果您要使用大量线程,请考虑使用线程池作为重用基础线程对象的一种方式(同时具有大量可运行对象-只是未运行)。

而且-由于您使用的是C#,因此异步任务(https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/)是一种更有效的策略。

通常-实现的简单性比效率更重要(在一定程度上)。您使用线程池描述的内容(以限制实际线程数)可能工作正常。