线程模型及上下文切换

记录下Java上下文切换的思考。

线程实现

线程的实现可分为三种方式用户空间实现内核空间实现混合实现

用户空间实现(N:1):操作系统仅感知进程,而感知不到线程,线程是在用户空间实现(线程创建、销毁及调度)。

N:1

用户空间实现其实类似于目前在PythonGolang中的协程的实现。

优点:线程的调度只是在用户态,减少了操作系统从内核态到用户态的切换开销
缺点:操作系统不敢知线程,需要设计线程的调度算法和线程对CPU资源的抢占使用,难度较大。

内核空间实现(1:1):操作系统感知进程和线程,线程是在内核空间实现(线程创建、销毁及调度)。

1:1

Linux已经基于NPTL实现了更符合POSIX标准的线程。

Linux中,线程被认为轻量级进程(LWP),它是建立在内核之上并由内核支持的用户线程(内核线程的高度抽象),每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。

混合实现(M:N):通过使用用户空间实现内核空间实现两种实现方式,即可以把线程管理交给内核,又可以利用内核态内切换开销小的特点。

M:N

特点:

  • 轻量级进程作为用户态和内核态的桥梁;
  • 尽量在用户态解决问题,避免内核态的切换;
  • 轻量级进程负责用户态到内核态的切换。

Java中到底才用了哪种实现和具体虚拟机的实现有关(一般采用1:1的内核实现)。

上下文切换

Linux是一个多任务操作系统,它支持远大于CPU数量的任务同时运行,通过将CPU轮流分配给它们,让用户感官上任务在同时运行。

多任务的分片执行必然会带来CPU依赖环境的切换,也就是上下文切换,其中包括CPU寄存器程序计数器

上下文切换

CPU寄存器:CPU内置的容量小、但速度极快的内存;
程序计数器:存储CPU正在执行的指令位置、或者即将执行的下一条指令位置;

上下文切换可以分为线程上下文切换进程上下文切换中断上下文切换

线程是调度的基本单位,进程则是资源拥有的基本单位。

内核调度的目标实际上对应的是线程,进程提供虚拟内存全局变量

上下文切换的内容:

  • 线程上下文切换虚拟栈寄存器
  • 进程上下文切换虚拟内存全局变量以及虚拟栈寄存器

一次系统调用实际上触发了两次上下文切换(用户态->内核态->用户态)。

Java线程模型

如果按照1:1模型,Java中的线程就是内核线程,通过内核来调度线程的切换。

线程私有部有程序计数器虚拟机栈,也是线程上下文切换时需要保存和加载的数据,其中,

  • 程序计数器:字节码行号指示器;
  • 虚拟机栈:存放局部变量。

Java虚拟机是一个独立的进程,在Java中其实不使用多进程编程,这里和其他语言不太一样。

那为什么Java中不使用多进程编程?

资源的隔离性,一个虚拟机实例对应着一个进程,同一个虚拟机仅负责对应进程的内存管理,如果有一个进程发生异常,并不影响其它的子进程。

Python中,存在GIL的原因导致多进程编程的存在。

一般来说么,多进程往往应用在隔离性很强的场景下,例如sandbox。

参考

https://blog.csdn.net/CringKong/article/details/79994511#12__16
https://segmentfault.com/a/1190000000663472
https://www.jianshu.com/p/2b27a5f30ce9
https://www.zhihu.com/question/23096638
http://wanggaoliang.club/2018/11/30/cpu%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2/