【性能】进程&线程&虚拟线程&协程

文章目录

  • 进程
  • 线程
  • 协程
  • 虚拟线程
  • 虚拟线程和协程的区别与相似
    • 相似
    • 区别
  • 来源

进程

在早期的单任务计算机中,用户一次只能提交一个作业,独享系统的全部资源,同时也只能干一件事情。进行计算时不能进行 IO 读写,但 CPU 与 IO 的速度存在巨大差异,一个作业在 CPU 上所花费的时间非常少,大部分时间在等待 IO。为了更合理的利用 CPU 资源,把内存划分为多块,不同程序使用各自的内存空间互不干扰,这里单独的程序就是一个进程,CPU 可以在多个进程之间切换执行,让 CPU 的利用率变高。为了实现 CPU 在多个进程之间切换,需要保存进程的上下文(如程序计数器、栈、内核数据结构等等),以便下次切换回来可以恢复执行。还需要一种调度算法,Linux 中采用了基于时间片和优先级的完全公平调度算法。

线程

多进程的出现是为了解决 CPU 利用率的问题,那为什么还需要线程?答案是为了减少上下文切换时的开销。

进程在如下两个时间点可能会让出 CPU,进行 CPU 切换:

  • 进程阻塞,如网络阻塞、代码层面的阻塞(锁、sleep等)、系统调用等
  • 进程时间片用完,让出 CPU

而进程切换 CPU 时需要进行这两步:

  • 切换页目录以使用新的地址空间
  • 切换内核栈和硬件上下文

进程和线程在 Linux 中没有本质区别,他们最大的不同就是进程有自己独立的内存空间,而线程(同进程中)是共享内存空间。

在进程切换时需要转换内存地址空间,而线程切换没有这个动作,所以线程切换比进程切换代价更小。

为什么内存地址空间转换这么慢?Linux实现中,每个进程的地址空间都是虚拟的,虚拟地址空间转换到物理地址空间需要查页表,这个查询是很慢的过程,因此会用一种叫做 TLB 的cache 来加速,当进程切换后,TLB 也随之失效了,所以会变慢。

综上,线程是为了降低进程切换过程中的开销。

协程

当我们的程序是 IO 密集型时(如 web 服务器、网关等),为了追求高吞吐,有两种思路:

  1. 为每个请求开一个线程处理,为了降低线程的创建开销,可以使用线程池技术,理论上线程池越大,则吞吐越高,但线程池越大,CPU 花在切换上的开销也越大

    线程的创建、销毁都需要调用系统调用,每次请求都创建,高并发下开销就显得很大,而且线程占用内存是 MB 级别,数量不能太多 为什么线程越多 cpu 切换越多?准确来说是可执行的线程越多,cpu 切换越多,因为操作系统的调度要保证绝对公平,有可执行线程时,一定是要雨露均沾,所以切换次数变多

  2. 使用异步非阻塞的开发模型,用一个进程或线程接收请求,然后通过 IO 多路复用让进程或线程不阻塞,省去上下文切换的开销

这两个方案,优缺点都很明显,方案1实现简单,但性能不高;方案2性能非常好,但实现起来复杂。有没有介于这两者之间的方案?既要简单,又要性能高,协程就解决了这个问题。

协程是用户视角的一种抽象,操作系统并没有这个概念,其主要思想是在用户态实现调度算法,用少量线程完成大量任务的调度。

协程需要解决线程遇到的几个问题:

  • 内存占用要小,且创建开销要小
  • 减少上下文切换的开销

第一点好实现,用户态的协程,只是一个数据结构,无需系统调用,而且可以设计的很小,达到 KB 级别。

第二点只能减少上下文切换次数来解决,因为协程的本质还是线程,其切换开销在用户态是无法降低的,只能通过降低切换次数来达到总体上开销的减少,可以有如下手段:

  • 让可执行的线程尽量少,这样切换次数必然会少
  • 让线程尽可能的处于运行状态,而不是阻塞让出时间片

多个协程分享操作系统分给线程的时间片,从而达到充分利用CPU算力的目的,协程调度器则决定了协程运行的顺序。如下图所示,线程运行调度器指派的协程,但每一时刻只能运行一个协程。
【性能】进程&线程&虚拟线程&协程_第1张图片

虚拟线程

们常见的Java线程与系统内核线程是一一对应的,系统内核线程调度器负责调度Java线程。为了提高应用程序的性能,我们会创建越来越多的Java线程,显然系统在调度Java线程时会消耗大量资源,来处理线程上下文切换。
【性能】进程&线程&虚拟线程&协程_第2张图片
近几十年来,我们一直依靠上述多线程模型来解决 Java 中的并发编程问题。为了提高系统的吞吐量,我们必须不断增加线程的数量,但是机器的线程很昂贵,可用线程的数量是有限的。尽管我们使用各种线程池来最大限度地提高线程的成本效益,但在 CPU、网络或内存资源被耗尽之前,线程往往成为我们应用程序性能的瓶颈,无法释放硬件应具有的最大性能。
【性能】进程&线程&虚拟线程&协程_第3张图片

为了解决这个问题,Java19 引入了虚拟线程。在 Java19 中,我们以前使用的线程称为平台线程,仍然与系统内核线程一一对应。大量 (M个) 的虚拟线程,运行在少量 (N个) 的平台线程上(与 OS 线程一一对应)(M:N 调度)。JVM调度多个虚拟线程在特定平台线程上执行,并且在平台线程上一次只执行一个虚拟线程。

虚拟线程的接口和普通线程是一样的,但是执行方式不一样。虚拟线程不是由操作系统调度,而是由普通线程调度,即成百上千个虚拟线程可以由一个普通线程调度。任何时刻,只能执行一个虚拟线程,但是,一旦该虚拟线程执行一个IO操作进入等待时,它会被立刻“挂起”,然后执行下一个虚拟线程。什么时候IO数据返回了,这个挂起的虚拟线程才会被再次调度。因此,若干个虚拟线程可以在一个普通线程中交替运行:

虚拟线程和协程的区别与相似

虚拟线程(Virtual Thread)是Java 19引入的一种轻量级线程,它在很多其他语言中被称为协程、纤程、绿色线程、用户态线程等。

相似

  • 虚拟线程和协程都很轻量级,它们的创建和销毁开销比传统操作系统线程要小。
  • 虚拟线程和协程都可以通过挂起和恢复来在线程之间切换,从而避免了线程上下文切换的开销。
  • 虚拟线程和协程都可以以异步和非阻塞的方式处理任务,提高了应用程序的性能和响应能力。

区别

虚拟线程是在JVM级别实现的,而协程是在语言级别实现的。因此,虚拟线程的实现可以与支持JVM的任何语言一起使用,而协程的实现需要特定的编程语言支持。

虚拟线程是协程的基于线程的实现,因此它们可以使用与线程相关的API,如ThreadLocal、Lock和Semaphore。协程不依赖于线程,通常需要特定的异步编程框架和API。

虚拟线程的调度由JVM管理,而协程的调度由编程语言或异步编程框架管理。因此,虚拟线程可以更好地与其他线程合作,而协程更适合处理异步任务。

总的来说,虚拟线程是一种新的线程类型,可以提高应用程序的性能和资源利用率,同时还可以使用传统的与线程相关的API。虚拟线程与协程有许多相似之处,但也存在一些不同之处。

虚拟线程确实可以使多线程编程更加简单和高效。与传统的操作系统线程相比,创建和销毁虚拟线程的开销更小,线程上下文切换的开销也更小,因此可以大大减少多线程编程中的资源消耗和性能瓶颈。

使用虚拟线程,开发人员可以像编写传统线程代码一样编写代码,而不必担心线程的数量和调度,因为JVM会自动管理虚拟线程的数量和调度。此外,虚拟线程还支持传统的与线程相关的API,如ThreadLocal、Lock和Semaphore,这使得开发人员更容易将传统线程代码迁移到虚拟线程中。

虚拟线程的引入使多线程编程更加高效、简单和安全,允许开发人员更多关注业务逻辑,而不必过多关注底层线程管理。

来源

如何理解协程和线程,以及它们之间的区别?
Java19 虚拟线程原理介绍及实现
java高级工程师

你可能感兴趣的:(开发,1024程序员节,进程,线程,虚拟线程,协程)