什么是多线程程序以及它是如何工作的



什么是多线程程序,它究竟是如何工作的?我读了一些文件,但我很困惑。我知道代码是逐行执行的,但我不明白程序是如何管理的
一个简单的答案将不胜感激。请举个例子(只有动画!)

什么是多线程程序,它究竟是如何工作的?

这个问题的有趣之处在于,有关于这个主题的完整书籍,但对很多人来说仍然难以捉摸。我将试着按照下面详述的顺序进行解释。

请注意,这只是提供一个要点,这样的答案永远无法达到所需的深度和细节。关于视频,我遇到的最好的是付费订阅的一部分(Wintellect和Pluralsight),如果你还没有订阅,请查看是否可以试听:

Jeffery Ritcher的Wintellect(来自他的书,CLR via C#,与线程基础有相同的章节)
  • Mike Woodring 的CLR线程

  • 解释顺序

    • 什么是线程
    • 为什么要引入线程,主要目的是什么
    • 陷阱以及如何使用同步结构来避免它们
    • 线程与线程池
    • API多线程编程的发展,如并行API、任务API
    • 并发集合,用法
    • 异步Await,线程但没有线程,为什么它们最适合IO

    什么是线程?

    它是软件实现,它纯粹是Windows操作系统概念(multi-threaded architecture),它是最低限度的工作单元。windows操作系统上的每个进程都至少有一个线程,每个方法调用都在该线程上完成。每个进程可以有多个线程,并行地执行多个任务(提供硬件支持)。其他基于Unix的操作系统是多进程架构,事实上,在Windows中,即使是像Oracle.exe这样最复杂的软件,也有用于不同关键后台操作的多线程单进程。

    为什么引入线程,主要目的是什么?

    与并发是主要目的的看法相反,正是健壮性导致了线程的引入,想象一下Windows上的每个进程都在使用相同的线程运行(在最初的16位版本中),其中一个进程崩溃,这在大多数情况下只意味着系统重新启动以恢复。并发操作线程的使用,因为每个进程中可以调用多个线程,这一点很明显。事实上,充分利用具有多核的处理器是非常重要的。

    陷阱以及如何避免使用同步结构?

    更多的线程意味着同时完成更多的工作,但当访问相同的内存时,问题就来了,尤其是对于Write,因为这可能会导致:

    1. 内存损坏
    2. 比赛条件

    另外,另一个问题是线程是一种非常昂贵的资源,每个线程都有一个线程环境块,内核内存分配。同样,为了调度处理器核心上的每个线程,需要花费时间进行上下文切换。滥用很可能会导致巨大的性能损失,而不是改进。为了避免与线程相关的损坏问题,根据需求使用同步结构(如lock, mutex, semaphore,)非常重要。读取总是线程安全的,但写入需要适当的同步。

    线程与线程池?

    真正的线程不是我们在C#.Net中使用的线程,它只是调用Win32线程的托管包装器。挑战仍然存在于用户严重滥用的能力上,比如调用远远超过所需数量的线程,分配处理器相关性,所以我们请求一个标准池来对工作项及其窗口进行排队,这不是更好吗?窗口决定何时需要新线程,何时现有线程可以调度工作项。线程是一种昂贵的资源,需要在使用中进行优化,否则可能是祸不单行。

    多线程编程的发展,如并行API、任务API

    从.Net 4.0开始,各种新的API Parallel.For、Parallel.ForEach用于数据并行化和任务并行化,使在系统中引入并发变得非常简单。这些API再次在内部使用线程池工作。任务更像是将工作安排在未来某个时间。现在引入并发是轻而易举的事,尽管仍然需要同步结构来避免内存损坏,但可以使用竞争条件或线程安全集合。

    并发集合,用法?

    类似ConcurrentBag, ConcurrentQueue, ConcurrentDictionary(System.Collections.Concurrent的一部分)的实现是固有的线程安全的,使用spin-wait,并且比显式Synchronization更容易、更快。也更容易管理和工作。还有另一组API,如ImmutableListSystem.Collections.Immutable,可通过nuget获得,它们是线程安全的,因为它们在内部创建了另一个数据结构副本。

    Async Await,有线程但没有线程,为什么它们最适合IO

    这是并发的一个重要方面,用于IO调用(磁盘、网络),迄今为止讨论的其他API用于基于计算的并发,因此线程很重要,使其更快,但对于IO调用,线程除了等待调用返回外别无用处,IO调用在基于硬件的队列IO Completion ports上处理

    在厨房里可以找到一个简单的类比。

    你可能以前用过食谱烹饪——从指定的食材开始,按照食谱中指示的步骤烹饪,最后你(希望)有一道美味的菜可以吃了。如果您这样做,那么您已经执行了传统(非多线程)程序

    但是,如果你必须做一顿完整的饭,其中包括许多不同的菜肴,该怎么办?做这件事的简单方法是从第一个食谱开始,按照食谱上说的做每一件事,做完后,把完成的菜(和第一个食谱)放在一边,然后从第二个食谱开始开始,按照它说的做,把第二个菜(和第二个方案)放在旁边,以此类推,直到你一个接一个地看完所有食谱。这是可行的,但你最终可能会在厨房里呆上10个小时,当然,当最后一道菜准备好吃的时候,第一道菜可能已经凉了,不好吃了。

    因此,你可能会像大多数厨师一样,同时开始制作几种食谱。例如,你可能会把烤肉放在烤箱里烤45分钟,但你不会坐在烤箱前等45分钟烤熟,而是花45分钟切蔬菜。当烤箱计时器响起时,你放下菜刀,把煮熟的烤肉从烤箱里拿出来冷却,然后再切蔬菜,以此类推。如果你能做到这一点,那么你就成功地多处理了几个食谱/程序。也就是说,你并不是一次处理多个食谱(你仍然只有两只手!),而是在必要时从一个食谱跳到另一个食谱,从而在多项任务上取得进展,而不是经常摆弄拇指。做好这一点,你就可以在更短的时间内吃完整顿饭,而且所有的东西都会在同一时间变得又热又新鲜。如果您这样做,您将执行一个简单的多线程程序

    然后,如果你想得到真正的美食,你可以雇佣其他几个厨师和你同时在厨房工作,这样你就可以在给定的时间内准备更多的食物。如果你这样做,你的团队就会进行多处理,每个厨师承担全部工作的一部分,所有厨师同时工作。请注意,正如前一段所述,每个厨师都可能在制作多个食谱(即多任务处理)。

    至于计算机是如何做这类事情的(不再类似于厨师),它通常使用一个准备运行的线程列表和一个计时器来实现它。当计时器关闭时(或者当当前执行的线程有一段时间无所事事时,因为它正在等待从慢速硬盘驱动器加载数据或其他什么),操作系统会执行上下文切换,暂停当前线程(将其放入某个列表中,不再执行该线程代码中的指令),然后从准备运行线程列表中提取另一个准备运行线程,并开始执行该线程代码中的指令。只要有必要,这种情况就会重复,通常每隔几毫秒就会发生一次上下文切换,这会给人一种错觉,即即使在单核CPU上,多个程序也"同时"运行。(在多核CPU上,它在每个核上都做同样的事情,在这种情况下,它不再只是一种幻觉;多个程序真的同时运行)

    为什么不参考微软自己的.net类System文档呢。线程。线

    它有一大堆用C#编写的简单示例程序(在页面底部),正如您所要求的:

    线程示例

    实际上多线程是同时处理多个进程。并且您可以并行地完成过程。

    它实际上是多线程的,同时处理多个进程。并且您可以并行地完成过程。您可以从主线程中获取任务,然后以其他方式执行并完成。

    最新更新