0000
User Level Thread,ULT,用户级线程:用户程序本身创建的线程
Kernel Level Thread,KLT,内核级线程:内核程序本身创建的线程
操作系统中线程是调度的基本单位,这里的线程指的是内核级线程。
用户程序是由编程语言开发,使用该编程语言创建线程是调用的该语言相关库和函数,这时创建出的线程统一称为用户线程,即当用户基于该编程语言创建一个线程,例如t = Thread(); t.run(),此时这里的线程不过是一个简简单单的对象,操作系统真正调度的是内核级线程,所以该语言线程相关的库会调用操作系统内核程序暴露的创建线程的接口,从而创建内核级线程,这时用户级线程(一个普通的代表线程的对象)会与内核级别线程绑定,从而当操作系统调度该内核级别线程时就完成了需要执行的任务。
内核级就是操作系统内核支持,用户级就是函数库实现(也就是说,不管你操作系统是不是支持线程的,我都可以在你上面用多线程编程)。
https://www.zhihu.com/question/35128513
每个用户线程只能竞争该进程内部的资源
内核级线程可以在全系统内进行资源的竞争
KLT对外暴露LWP的接口,Light Weight Process,轻量级进程,即线程
P代表用户轻量级进程,即用户线程
P会通过LWP与KLT进行映射
KLT被操作系统调度
P与KLT的映射关系有三种,一对一、多对一、多对多,即三种多线程模型。
一对一模型
一个用户线程对应一个内核线程
优点:
- 映射关系简单
- 线程之前相对独立
- 用户线程被调度的逻辑完全与操作系统一致
缺点:
- 一些操作系统限制了内核线程的数量,用户线程的数量也会受到限制
- 用户线程上下文切换也意味着内核线程上下文切换
多对一模型
将多个用户线程映射到一个内核线程上,线程之间的切换由用户态的代码来实现,系统内核感受不到线程的实现方式。
优点:
- 用户线程的建立、同步、销毁都在用户态中完成,用户线程的上下文切换不会导致内核线程上下文切换
- 用户线程的数量不会受到内核线程数量的限制
缺点:
- 一个用户线程导致调用了内核级别的线程阻塞,则会导致所有用户线程阻塞
- 无法充分利用多处理器优势,因为一个进程的所有用户线程都映射到一个内核线程了
多对多模型
- 将多个用户线程映射到多个内核线程上。
- 由线程库负责在可用的可调度实体上调度用户线程,这使得线程的上下文切换非常快,因为避免了系统调用。
优点:
- 一个线程的阻塞不会导致所有的线程阻塞,因为此时还有别的内核线程调度来执行。
- 多对多模型对用户线程的数量没有限制
- 在多处理器的模型中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一的大
缺点:
- 映射复杂
多线程模型取决于具体平台和语言实现
Windows
Windows(从Win95开始)和Linux都实现了线程的一对一模型。
Java
Java线程在JDK1.2之前,是基于称为“绿色线程”(Green Threads)的用户线程实现的,而在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现。因此,在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对Java程序的编码和运行过程来说,这些差异都是透明的。
在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没达成一致,Java虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。对于Sun JDK来说,它的windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。而在Solaris平台中,由于操作系统的线程特性可以同时支持一对一(通过Bound Threads或Alternate Libthread实现)及多对多(通过LWP/Thread Based Synchronization实现)的线程模型,因此在Solaris版的JDK也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:+UseBoundThreads来明确指定虚拟机使用哪种线程模型
前文提到了用户级线程和内核级别线程的映射关系和映射模型,我们知道了:
- 用户级线程和内核级线程是对应的,用户级线程不会自己凭空跑起来,最终都会落到一个内核线程当中去执行。
- 用户线程的创建依赖于App开发者(Java中指JVM的开发者),而系统线程的创建依赖于内核,并且各自管理两种线程。
既然用户线程需要用到内核线程,那么对应的平台(操作系统)就必须为我们提供相关的操作库或者接口等等,来实现用户线程管理方式的设计和编写。
各自的平台都有各自的Api,比如Window下的Java,使用的线程库就是Win Api,Unix类的系统下,使用的就是PThread。
在JDK1.2版本中,Java自行开发了一套用户线程管理工具,名叫:绿色线程,实现方法我们不去细究,我们需要知道,它是用来做用户线程管理的一套方案即可,它解决的是用户线程和内核线程的映射。
绿色线程存在的问题,就是前面说到的,用户线程自行管理将无法被操作系统感知到,需要用户进行自行调度,无法更好地利用到当今CPU的多核心并行能力。
在JDK1.2之后,Java选择了更加稳定、方便的操作系统原生的内核级线程,直接通过系统调用,将线程的调度交还给了操作系统的内核,而不再去自己管理用户线程。这就是我们常说的,Java线程会直接对应到系统线程之上。
JVM自身会屏蔽掉操作系统带来的一些差异,为上层的字节码执行提供稳定的运行环境,但是操作系统本身的种种差异也会导致一些不同,JVM的线程映射到系统线程之后,具体的情况需要由操作系统的线程模型定义,一个Java线程被阻塞了,不一定内核线程就被阻塞了,因为很可能在一些其他的操作系统中,提供了1:1之外的内存映射关系,可能内核线程被内核调度走了去处理其他事情,所以,Java的内核状态并不一定能完全代表当前系统内核的状态。
作者:开中断
链接:https://juejin.cn/post/7085227749215830023
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在Java中,我们平时所说的并发编程、多线程、共享资源等概念都是与线程相关的,这里所说的线程实际上应该叫作“用户线程”,而对应到操作系统,还有另外一种线程叫作“内核线程”。
用户线程位于内核之上,它的管理无需内核支持;而内核线程由操作系统来直接支持与管理。几乎所有的现代操作系统,包括 Windows、Linux、Mac OS X 和 Solaris,都支持内核线程。
最终,用户线程和内核线程之间必然存在某种关系,本章我们一起来学习下建立这种关系常见的三种方法:多对一模型、一对一模型和多对多模型。
(1)线程分为用户线程和内核线程;
(2)线程模型有多对一模型、一对一模型、多对多模型;
(3)操作系统一般只实现到一对一模型;
(4)Java使用的是一对一线程模型,所以它的一个线程对应于一个内核线程,调度完全交给操作系统来处理;
(5)Go语言使用的是多对多线程模型,这也是其高并发的原因,它的线程模型与Java中的ForkJoinPool非常类似;
(6)python的gevent使用的是多对一线程模型;
https://blog.csdn.net/mccand1234/article/details/118465728
https://developer.volcengine.com/articles/7413184718121730102
https://blog.huohaodong.com/blog/project-loom-tasting
https://blog.csdn.net/qyb19970829/article/details/113621043
总结一下,现在大部分都是一对一模型,不过天道好轮回,现在为了更进一步提高利用率,引入了协程。
轻量级进程(LWP)是一种由内核支持的用户线程,如果用户线程完全由轻量级进程构成,可以说是轻量级进程就是线程
Go
总结一下go协程的特点:
协程间需要保证数据安全,比如通过channel或锁。
可以利用多核并行执行。
协程间不完全同步,可以并行运行,具体要看channel的设计。
抢占式调度,可能无法实现公平。
MPG
并发编程模型
CSP模型,通信顺序进程模型
CSP模型的核心思想是“不要通过共享内存来通信,而要通过通信来共享内存”。
ch := make(chan int) // 创建一个整型channel
go func() {
ch <- 42 // 发送数据到channel
}()
value := <-ch // 从channel接收数据
fmt.Println(value) // 输出:42