从 Linux 内核线程反观 Java Go 的线程模型

简介: > 原文参考我的个人公众号文章(欢迎关注!):[点此链接进入](https://mp.weixin.qq.com/s?__biz=MzkxNDMyNjk0Mw==&mid=2247484374&idx=1&sn=5e5c8ef7adc0841019cb3302ed5003db&chksm=c1715726f606de301494abf87fbdc40303bdb9ac8393333b0778e23
原文参考我的个人公众号文章(欢迎关注!): 点此链接进入

为什么要从 Linux内核线程角度出发去分析开发语言的线程模型?

这里且不说 Linux 为什么是流行的操作系统,每当提到这个话题,因为Linux开源、稳定等等,这里不说那么多题外话。之所以从 Linux 内核角度出发,是因为操作系统是每个热衷于技术的程序员心之所向的地方,并且很多开发语言都或多或少对操作系统有依赖。

为什么本文选择 Java 和 Go 来分析其线程模型?

所有与 IT 相关的科班专业,如计算机科学与技术、网络工程等等,必修 C 语言,C 语言是最贴近硬件的语言,是很多操作系统的开发语言。也正因为如此 C 语言对与开发人员来说并不是那么友好,比如组织大型的项目,比如融入更多的设计模式等。Java 和 Go 抛开目前作为主流开发语言的原因,Java 和 Go 都在高并发编程层面有各自的建树,尤其是 Go 语言号称是专为并发而生的开发语言。

Linux 内核线程

内核线程是直接有内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,与其他进程是并行执行的(同时也并行与内核自身的执行)。内核线程经常被称为(内核)守护进程。
内核进程的两个特点:

  1. 他们在cpu的管理态执行(supervisor mode),而并不是用户态
  2. 他们只可以访问虚拟地址空间的内核部分,但不能访问用户空间

内核线程其他的细节这里就暂不细说,只需要知道其两个特点我们就可以进一步分析线程的实现,如果感兴趣可以就虚拟地址空间的内核部分、以及针对下面线程实现方式中 用户态 与 内核态 切换 去进一步探究(这里就不细说了,默认大家都知道,就不老生常谈了)。

线程的三种实现方式

  1. 使用内核线程实现,用户线程数和内核线程数为1:1
  2. 使用用户线程实现,用户线程数和内核线程数为N:1,每个用户线程对应一个轻量级线程(内核线程高级接口:轻量级线程LWP)
  3. 使用用户线程+轻量级线程实现,用户线程和轻量级线程为N:M
  • 内核实现

  • 用户线程实现

  • 混合实现

Java 线程模型

Java 线程的实现不受 Java 虚拟机规范的约束,在早期 JVM 上(JDK1.2以前),通过一种“绿色线程”(Green Threads)的用户线程实现的,但从 JDK1.3 开始,主流商用虚拟机模型普遍采用操作系统与安生线程模型来实现 ,即采用操作系统内核线程实现。
以HotSpot为例,它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以HotSpot自己是不会去干涉线程调度的(可以设置线程优先级给操作系统提供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的。

Go 线程模型

Go 作为专为并发而生的语言,当然有其独有的东西:两级线程模型。

两级线程模型


用户线程与 内核线程 为多对多(N:M)的映射关系。两级线程模型吸收前两种线程模型的优点并且尽量规避了它们的缺点,区别于用户级线程模型,两级线程模型中的进程可以与多个内核线程 关联,也就是说一个进程内的多个线程可以分别绑定一个自己的 内核线程,这点和内核级线程模型相似;其次,又区别于内核级线程模型,它的进程里的线程并不与内核线程 唯一绑定,而是可以多个用户线程映射到同一个 内核线程,当某个 内核线程 因为其绑定的线程的阻塞操作被内核调度出 CPU 时,其关联的进程中其余用户线程可以重新与其他 内核线程 绑定运行。所以,两级线程模型既不是用户级线程模型那种完全靠自己调度的也不是内核级线程模型完全靠操作系统调度的,而是一种自身调度与系统调度协同工作的中间态,即用户调度器实现用户线程到 内核线程 的调度,内核调度器实现 内核线程 到 CPU 上的调度。

Go 特有的两级线程模型 GMP

由 Go 调度器实现 Goroutine 到 KSE 的调度,由内核调度器实现 KSE 到 CPU 上的调度。Go 的调度器使用 G、M、P 三个结构体来实现 Goroutine 的调度,也称之为GMP 模型。
G:表示 Goroutine。每个 Goroutine 对应一个 G 结构体,G 存储 Goroutine 的运行堆栈、状态以及任务函数,可重用。当 Goroutine 被调离 CPU 时,调度器代码负责把 CPU 寄存器的值保存在 G 对象的成员变量之中,当 Goroutine 被调度起来运行时,调度器代码又负责把 G 对象的成员变量所保存的寄存器的值恢复到 CPU 的寄存器
M:OS 底层线程的抽象,它本身就与一个内核线程进行绑定,每个工作线程都有唯一的一个 M 结构体的实例对象与之对应,它代表着真正执行计算的资源,由操作系统的调度器调度和管理。M 结构体对象除了记录着工作线程的诸如栈的起止位置、当前正在执行的 Goroutine 以及是否空闲等等状态信息之外,还通过指针维持着与 P 结构体的实例对象之间的绑定关系
P:表示逻辑处理器。对 G 来说,P 相当于 CPU 核,G 只有绑定到 P(在 P 的 local runq 中)才能被调度。对 M 来说,P 提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等。它维护一个局部 Goroutine 可运行 G 队列,工作线程优先使用自己的局部运行队列,只有必要时才会去访问全局运行队列,这可以大大减少锁冲突,提高工作线程的并发性,并且可以良好的运用程序的局部性原理
一个 G 的执行需要 P 和 M 的支持。一个 M 在与一个 P 关联之后,就形成了一个有效的 G 运行环境(内核线程+上下文)。每个 P 都包含一个可运行的 G 的队列(runq)。该队列中的 G 会被依次传递给与本地 P 关联的 M,并获得运行时机。
M 与 KSE 之间总是一一对应的关系,一个 M 仅能代表一个内核线程。M 与 KSE 之间的关联非常稳固,一个 M 在其生命周期内,会且仅会与一个 KSE 产生关联,而 M 与 P、P 与 G 之间的关联都是可变的,M 与 P 也是一对一的关系,P 与 G 则是一对多的关系。

目录
相关文章
|
30天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
2月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制
本文深入探讨了Linux操作系统中用于管理多线程和进程的并发控制的关键技术,包括原子操作、锁机制、自旋锁、互斥量以及信号量。通过详细分析这些技术的原理和应用,旨在为读者提供一个关于如何有效利用Linux内核提供的并发控制工具以优化系统性能和稳定性的综合视角。
|
2月前
|
缓存 负载均衡 算法
深入探索Linux内核的调度机制
本文旨在揭示Linux操作系统核心的心脏——进程调度机制。我们将从Linux内核的架构出发,深入剖析其调度策略、算法以及它们如何共同作用于系统性能优化和资源管理。不同于常规摘要提供文章概览的方式,本摘要将直接带领读者进入Linux调度机制的世界,通过对其工作原理的解析,展现这一复杂系统的精妙设计与实现。
101 8
|
4天前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
32 15
|
29天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
29天前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
30天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
30天前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
1月前
|
负载均衡 算法 Linux
深入探索Linux内核调度机制:公平与效率的平衡####
本文旨在剖析Linux操作系统内核中的进程调度机制,特别是其如何通过CFS(完全公平调度器)算法实现多任务环境下资源分配的公平性与系统响应速度之间的微妙平衡。不同于传统摘要的概览性质,本文摘要将直接聚焦于CFS的核心原理、设计目标及面临的挑战,为读者揭开Linux高效调度的秘密。 ####
37 3
|
2月前
|
负载均衡 算法 Linux
深入探索Linux内核调度器:公平与效率的平衡####
本文通过剖析Linux内核调度器的工作机制,揭示了其在多任务处理环境中如何实现时间片轮转、优先级调整及完全公平调度算法(CFS),以达到既公平又高效地分配CPU资源的目标。通过对比FIFO和RR等传统调度策略,本文展示了Linux调度器如何在复杂的计算场景下优化性能,为系统设计师和开发者提供了宝贵的设计思路。 ####
43 6