转载

Java性能 -- 协程

  1. 轻量级进程内核线程 一对一相互映射实现的 1:1 线程模型
  2. 用户线程内核线程 实现的 N:1 线程模型
  3. 用户线程轻量级进程 混合实现的 N:M 线程模型

1:1线程模型

  1. 内核线程( Kernel-Level Thread )是由操作系统 内核 支持的线程,内核通过 调度器 对线程进行调度,负责完成线程的 切换
  2. 在Linux中,往往通过 fork 函数创建一个 子进程 来代表一个 内核中的线程
    • 一个进程调用fork函数后,系统会先给新的子进程 分配资源 ,然后 复制 主进程,只有少数值与主进程不一样
  3. 采用fork的方式,会产生大量的 冗余 数据,占用 大量内存空间 ,也会消耗 大量CPU时间 来初始化内存空间和复制数据
  4. 如果是一模一样的数据,可以 共享 主进程的数据,于是轻量级进程( Light Weight Process,LWP )出现了
    • LWP使用 clone 系统调用创建线程
    • clone函数将 部分 父进程的资源的数据结构进行复制,复制内容 可选 ,且没有被复制的资源可以通过 指针 共享给子进程
    • LWP 运行单元更小运行速度更快 ,LWP和内核线程 一一映射 ,每个LWP都是由一个内核线程支持

N:1线程模型

  1. 1:1线程模型的缺陷
    • 在线程创建、切换上都存在 用户态和内核态的切换
    • 系统资源有限, 无法支持创建大量LWP
  2. 该线程模型在 用户空间 完成了线程的创建、同步、销毁和调度,并不需要内核的帮助, 不会产生用户态和内核态的空间切换

N:M线程模型

  1. N:1线程模型的缺陷
    • 操作系统无法感知用户态的线程 ,容易造成某个线程进行 系统调用 内核线程时被阻塞,从而导致 整个进程被阻塞
  2. N:M线程模型是一种 混合 线程管理模型
    • 支持 用户态线程 通过 LWP内核线程 连接, 用户态的线程数量内核态的LWP数量 是N:M的映射关系

Java线程 / Go协程

  1. Java线程
    • Thead#start通过调用native方法 start0 实现
    • 在Linux下,JVM Thread是基于 pthread_create 实现的,而pthread_create实际上调用了 clone 系统调用来创建线程
    • 所以,Java在Linux下采用的是 1:1线程模型 (用户线程与轻量级线程一一映射),线程通过 内核调度 ,涉及 上下文切换
  2. Go协程
    • Go语言使用了 N:M线程模型 实现了 自己的调度器 ,在 N个内核线程上多路复用M个协程
    • 协程的上下文切换在 用户态协程调度器 完成, 不需要陷入到内核 ,相比Java线程, 代价很小

协程的实现原理

  1. 协程可以看作 一个类函数 或者 一块函数中的代码 ,可以在主线程里面轻松创建多个协程
  2. 程序调用协程和调用函数是不一样的,协程可以通过 暂停 或者 阻塞 的方式将协程的执行挂起,而其他协程可以继续执行
    • 协程的挂起只是在程序中( 用户态 )的挂起,同时将代码 执行权 转让给其他协程使用
    • 待获取执行权的协程执行完之后,将从挂起点唤醒挂起的协程
    • 协程的 挂起唤醒 是通过一个 调度器 完成的

图例解释

Java性能 -- 协程
  1. 假设程序中默认创建两个线程为协程使用,在主线程中创建协程ABCD…,分别存储在 就绪队列
  2. 调度器首先会分配工作线程A执行协程A,工作线程B执行协程B,其他创建的协程将会在 等待队列 中进行排队等待
  3. 当协程A调用 暂停方法被阻塞 时,协程A会进入到 挂起队列 ,调度器会调用 等待队列 中的其他协程 抢占 线程A执行
  4. 当协程A 被唤醒 时,它需要重新进入到 就绪队列 中,通过调度器 抢占 线程
    • 如果抢占成功,就继续执行协程A;如果抢占失败,就继续等待抢占线程

线程 / 协程

  1. 相比于线程,协程少了由于同步资源 竞争 带来的 CPU上下文切换
  2. 应用场景: IO阻塞型场景
    • 比较适合 IO密集型 的应用,特别在 网络请求 中,有较多的时间在等待服务端响应
      • 协程可以 保证线程不会阻塞在等待网络响应 (可以在协程层面阻塞)中,充分利用了多核多线程的能力
    • 对于CPU密集型的应用,由于多数情况下CPU都比较繁忙,协程的优势就不会特别明显
  3. 线程是通过 共享内存 的方式来实现数据共享,而协程是使用了 通信 (MailBox)的方式来实现数据共享
    • 这主要为了避免内存共享数据而带来的 线程安全 问题

小结

  1. 协程可以认为是 运行在线程上的代码块 ,协程提供的 挂起 操作会使 协程暂停执行 ,而 不会导致线程阻塞
  2. 协程是一种 轻量级资源 ,即使创建上千个协程,对系统来说也不会是很大的负担,而线程则不然
    • 协程的设计方式极大地提高了线程的使用率
原文  http://zhongmingmao.me/2019/09/02/java-performance-coroutine/
正文到此结束
Loading...