用户态、内核态,进程、线程这些概念经常是脱口而出,但是并没有特别理解这些概念背后的含义。比方说,对于java的多线程,有的地方说java多线程线程切换的时候会涉及用户态切换到内核态、进程上线文切换、线程上下文切换,会耗时,我们真的了解这些个过程到底是在干啥,为啥会耗时么?有的时候傻傻分不清楚。所以学习观摩了一下,整理成读书笔记,记录自己的理解。
所以这个文档仅仅是尝试将自己学习了解的,用我熟悉的理解方式记录下来,对linux底层的东西我并不熟悉,所以不是在传递linux编程的知识点,只是一些我认为的通俗易懂的方式来记录我学习后对着几个概念的理解。
用户态 vs 内核态
我觉得这篇文章虽然很短,但是是将为什么linux操作系统有内核态和用户态,以及切换过程讲的比较清晰通俗易懂的:https://blog.csdn.net/ddna/article/details/4941373
下面我结合自己的理解,从软件分层和焦点分离的角度来加深理解。
借助分层思想来理解用户态和内核态,操作系统要实现对各种计算机资源进行管理是非常复杂的,而且对安全性、可靠性要求又比较高,不过好在这些管理逻辑都具有一定的通用性。比如对内存的管理,不管内存条是什么样的,那么内存管理的逻辑都是一样的等等。那么就可以将这些通用的复杂的逻辑进行封装到一个地方来实现,在这个地方来专门考虑其安全性、可靠性等,这个地方就是操作系统内核。
那从这个角度,说白了操作系统内核就是一堆非常复杂、安全性要求比较高的代码逻辑,这一堆代码实现了对计算机各种资源的管理。如果需要使用这些资源,就只能调用内核对外提供的这些接口,不能自己去实现一套资源管理,直接去操作计算机资源。比如要从磁盘上读取文件,只能调用内核对外提供的文件管理相关的接口,而不能自己直接去访问磁盘。
从分层的角度来说,内核的职责就是屏蔽掉这些计算资源复杂的管理逻辑,而上层应用程序不感知,只管调用内核的接口使用这些资源就好了,主要焦点也就分离开了。
所以从这个角度,区分内核态和用户态的好处就是焦点分离,然后焦点分离会带来一些列的好处:
1. 用户态的应用程序不用关系这些复杂的计算资源的管理,只是需要使用内核提供的能力使用这些资源就好,将重心放在应用程序的逻辑上。
2. 更加安全。如果让用户态的应用程序直接去操作计算资源,各种中各样的应用参差不齐,实现应用的人的水平也参差不齐,那么都去实现这么复杂的计算资源管理逻辑,很容易就将整个底层的资源给搞乱掉,导致全盘皆输。也就是说一个应用程序不靠谱会导致整个机器上的所有应用都跪掉
从分层的角度讲,软件分层其实更多的是个约定,现在几乎没人使用系统校验的方式去强制层间的调用关系,但是对于操作系统来说,是不允许用户应用程序直接去操作这些计算资源,只能通过调用内核的服务来使用计算机资源,所以操作系统会有校验,哪些命令是不允许用户态的应用程序来执行的,用户态每次执行指令的时候,都会校验一下,是否这个命令是否只能内核来执行。
那么用户态是否是完全不能直接访问任何资源呢?从计算机的角度来说,不可能有哪个操作不依赖于底层的计算资源,那如果是这样,那就没法搞了,内核需要提供完成计算机上所有事情的基本操作,所以操作系统将资源进行了分类:一类是允许用户态的程序直接访问操作的,这类资源的集合就叫做用户空间,一类是只能用户不能直接访问,只能通过内核去访问的,这类资源的集合就是内核空间。比如内存分配的操作就只能由内核来完成,那存储内存分配指定的地方就在内核空间中;当一块内存分配完成,分配给了某个应用程序,那这块内存就属于用户空间,用户应用程序就可以直接访问了,对用户空间的内存的访问就不需要内核的参与了。
再来看怎么实现内核空间和用户空间呢?
这个是有cpu硬件的支持的。这种分层管理的思想对操作系统来说比较重要,现在cpu引入了特权级的概念,不同特权级能够执行的cpu指令是不一样的,比如一款cpu一共300个指令,特权级=0的可以执行所有的指令,特区及=1的可以执行其中200个,特权级=3的就只能执行其中的50个,ps:0级能执行什么样的指令,1级能执行什么样的指令并不是cpu决定的,而是说cpu提供了这个机制,由上层的操作系统来实现。对弈x86的cpu来说,提供了4个特权级:0~3。但是linux系统只使用了其中的两个:0级和3级。0级就是所谓的内核态,3级就是用户态。然后规定了哪些操作只能再0级才能访问,这部分统一封装到操作系统的内核中。
所以理解几个概念:
1. 操作系统内核:这个其实就是操作系统最为核心的代码,只有当cpu的特权级=0的时候,才可以执行这部分的代码(准确的说是这部分代码翻译后的cpu指令)
2. 用户态/内核态:这个是指cpu当前执行的代码的内容,如果cpu当前执行的是用户态的代码逻辑(特权级=3),那么就说当前cpu当前的状态处于用户态;如果cpu当前执行的是操作系统内核的代码(cpu特权级=0),那么就说当前cpu处于内核态。另外内核态/用户态是从linux操作系统的角度来看cpu的,实际上cpu本身就只有特权级,并没有什么用户态/内核态,只是linux操作系统中将cpu特权级=0叫做内核态,因为这个时候cpu只会执行linux操作系统内核中的代码,而当cpu执行非linux内核中代码的时候,cpu的特权级=3,成为用户态。
3. 用户空间/内核空间,这个我理解也是为了方便理解操作系统这种分层设计而发明的概念,所以的用户空间就是cpu在用户态执行的那些命令可以访问的资源,而内核态就是只有cpu在执行linux操作系统的内核代码的时候才能访问的那些资源。简单粗暴理解下,用户空间就是cpu处于用户态时可以执行的指令;内核态就是cpu处于内核态才可以执行的指令(因为这些指令的背后其实就会去访问不同的计算机资源)
下一个问题就是,用户态如何切换到内核态,使用内核的能力:
- 系统调用。这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
- 异常。当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
- 外围设备的中断。当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
虽然有三种方式可以从用户态切换到内核态,但是只有系统调用是我们说的用户态需要使用内核态的能力去访问计算机资源的情况,其他两种情况,说白了都是linux操作系统为了实现资源管理的一些机制。所以我们看内核提供了哪些能力,可以从内核提供了哪些系统调用上来看:https://blog.csdn.net/orange_os/article/details/7485069?spm=1001.2014.3001.5501
从上面的描述中,所谓的系统调用,无非是用户态的应用程序调用内核提供的接口,那为啥说系统调用比较耗时呢?
感性的认识一下:对于一个cpu,统一时刻只能运营一条指令,这个应该比较好理解,如果某个时刻cpu正好在用户态,即在执行用户应用程序的指令,这个时候应用程序发起了一个系统调用,比如创建进程的fork(),或者创建线程的p_thread_create(),那么这个时候就要求cpu暂停用户态的指令的执行,将特权级设置成0,然后去执行fork()函数背后对应的指令。感性认识下这个过程需要干什么事情:
1. 要记录下用户态当前执行指令的现场,比如p_thread_create()的下一条指令是啥,这样p_thread_create()执行完后,退出内核态,cpu才知道该执行什么指令,现场还包括应用程序按当前的堆栈。
2. 要将cpu的特权级设置为0,然后将p_thread_create()对应的指令加载到cpu的寄存器,然后准备对应的资源。
3. p_thread_create()相关指令执行完后,将结果返回给用户态的应用程序。
4. 将cpu的特权级设置为3,然后将第一步保存的现场加载到cpu的寄存器,继续执行用户态的指令。
这里额外的耗时主要就是切换时候的现场保留以及加载,如果不分内核态和用户态,是不是就不需要额外的保存用户态应用程序的线程信息了呢?直接就执行了。详细的切换:https://blog.csdn.net/shanghx_123/article/details/83151064、https://blog.csdn.net/ddna/article/details/4941373
进程 vs 线程
先上一个常见的多进程变成套路:
fork--execve--wait
1. fork() 创建一个子进程
2. execve()让子进程执行指定的代码
3. 子进程退出后变成一个僵尸进程,僵尸进程中保存了进程执行期间的一些信息,如退出原因、占用cpu时间等等。这种僵尸进程一方面等父进程来回收,一方面可以调用wait/waitpid将僵尸进程中的数据收集,并收集僵尸进程中的数据。
这个文章中举例说明系统调用的时候,就是用进程了进程创建的例子:https://blog.csdn.net/orange_os/article/details/7485069?spm=1001.2014.3001.5501
关于进程和线程的理解比较好的(解释了进程线程概念并从各个方面比较了进程和线程的区别 ):https://www.cnblogs.com/fah936861121/articles/8043187.html
多线程编程
创建进程的方式:https://blog.csdn.net/rock_joker/article/details/72722008
先感性的去了解怎么利用多进程和多线程实现多任务并发,然后再来理解后面的内容,我感觉好理解写,然后再回过头来看,就清洗很多了
进程:资源分配的基本单位
线程:cpu调度的最小单位
1. 进程是资源分配的基本单位,那进程是不是不能被调度。当然不是,进程是可以被单独调度,获得cpu执行进程中制定的代码的。只是说如果要分配资源,那么就只能以进程维度来分配。
2.线程是cpu调度的最小单位,那是不是进程就没有任何资源。当然不是,线程是依托进程而存在的,这个进程下所有的线程共享分配给进程的资源。但是并不是说线程没有自己独享的资源,至少线程调用栈一定是线程独享的,只是说线程栈锁占用的资源也是分配给进程的一部分。
从linux创建进程和线程的的方式看:
进程创建:
创建进程是利用了fork()这个系统调用。调用fork()后会copy一个和父进程一抹一样的进程出来,包括父进程的资源,也会copy一份一模一样的一份出来(虽然说是copy其实就是需要为fork出来的新进程分配新的资源)。这就有一个问题,我们创建一个新的进程,目的更多的是用来执行和父进程不一样的代码的,完全copy一份父进程,那执行的代码也是一样,copy意义是啥呢?所以还有一个execve的系统调用,作用就是给线程指定可执行内容,比如可执行的二进制文件、linux的脚本等。所以多线程编程的场景中更多的是:fork()创建一个新进程、然后调用execve()给新进程指定新的可执行内容。那这里就有一个问题,fork()会copy一份和父进程一样的资源出来,然后接着调用execve()又会替换掉copy出来的很多资源,那花大力气copy的东西岂不是浪费了(刚刚copy完就覆盖了),所以后来对fork()做了优化--写时复制。也就是说在fork()的时候并不会立即copy父进程的资源,copy的只是一个资源映射表(这比实际的内存要小太多了),当实际操作内存的时候,才按需复制父进程的内容。
fork()会创建一个子进程,并且copy父进程的资源。我们换个角度将,进程是资源分配的基本单位,如果fork的进程不包含资源,但是又可以独立被调度,那这个进程算是线程么?从两方面理解:
1. 我们也说了,线程除了有自己独享的调用栈等意外,是可以共享父进程的资源的,也就是说,一个进程下的多个线程都是可以直接访问分配给进程的内存的。
2. 再看fork的子进程,一个fork的子进程可以访问父进程的资源么? 是不可以的,它是将父进程的资源copy到自己的空间来,但是不能直接去访问父进程的资源。
3. fork的进程不包含任何资源是错误的说法,fork是创建一个进程,创建过程中就会给进程分配资源,比如内存,只是说内存中的内容是空的,不是将父进程中的内容全部copy过来,并不是说子进程不包含资源,它也是申请了内存资源的。
所以,一个进程fork的子进程,和这个进程下创建的线程的最大区别就在于:1. 子进程不能访问父进程的资源,子进程需要被分配独立的资源。2. 线程是可以直接访问进程的资源的,并且创建线程不会去想操作系统额外的申请资源,线程的调用栈这些资源都是进程原有的。
fork()创建进程的方式是copy父进程,那父进程从哪儿来呢?
计算机开机启动的时候,第一个被创建出来的进程是0号进程,但是这个进程在操作系统层面是不可见的,0号进程完成了操作系统的功能加载与初期设定,然后它创造了1号进程(init),这个进程就是用来管理整个操作系统的,使用pstree命令查看进程树,可以发现最根上的进程就是这个1号进程(init进程)。然后操作系统中很多管理进程都是通过1号进程fork出来的。而我们多任务变成中fork出来的进程非父进程都是1号进程的子孙。
线程创建:
线程创建的系统调用是clone(),clone()有参数控制,clone过程中需要从进程中复制哪些数据,因为线程也需要自己独立的调用栈,这个调用栈的初始状态只能从进程中复制而来。有个问题:jvm创建线程是调用的p_thread_create(),不是clone()呀,是什么情况。clone()是linux内核提供的一个系统调用,用于创建线程的,而p_thread_create()是linux的一个库函数,即POSIX线程库中的一个函数而已,它内部很大可能也是封装了clone()系统调用来最终实现线程的创建的。linux操作系统中提供了两个线程库:Linux-Native线程库和POSIX线程库,p_thread_create()就是POSIX线程库中的一个函数。其实道理很简单:linux内核只是实现了最底层的计算机资源管理的逻辑,并对外提供了一些系统调用的接口,但是直接使用这些接口实现编程,可能门槛会更高,所以就有了很多函数库,对其进行进一步的封装,方便应用的编程实现。POSIX就是一个官方专门用于多线程变成的库。
大概明白了多进程和多线程的编程方式,那么什么时候用多进程?什么时候用多进程
首先明白两个的特点:
1. fork()的多进编程,资源空间是独立的,而且资源是从父进程copy过来的
2. clone()的多线程,除了线程运行所需要的必要信息,如调用栈以外,不会有更多的独享的资源,它是直接访问进程中的资源的。
正式各自的特点,不同场景就有不同的应用:
比如多任务的TCP程序的服务端,父进程执行accept()一个客户端连接请求之后会返回一个新建立的连接的描述符,然后通过这个资源描述符去socket中获取数据。如果accept()到一个新的客户端连接的soket后,使用的是fork()一个子进程,那么因为fork()会从父进程copy数据,那么就直接会将父进程accept()到的这个socket的文件描述符copy到子进程,这样子进程就可以直接基于copy的这份数据去接受通道中的数据/向通道发送数据了。然后父进程继续等待其他的socket链接。
但是如果父进程accept()到socket后,换成多线程处理,由于新增的线程不会copy进程中的数据,那么这个时候就只能子线程去copy父进程的accept()到的这个socket文件描述符到自己的调用栈空间里去,而父进程必须保证子线程复制完成后才能继续接受其他客户端的链接了。但这执行起来并不简单,因为子线程与父线程的调度是独立的,父线程无法知道子线程何时复制完毕。这又得发生线程间通信,子线程复制完成后主动通知父线程。这样一来父线程的处理动作必然不能连贯,比起多进程环境,父线程显得效率有所下降。
多线程资源不独立,在tcp这个场景看似是个缺点,但在有的情况下就成了优点。多进程环境间完全独立,要实现通信的话就得采用进程间的通信方式,它们通常都是耗时间的。而线程则不用任何手段数据就是共享的。当然多个子线程在同时执行写入操作时需要实现互斥,否则数据就写“脏”了。
进程通信和线程通信
1. 共享内存 :共享内存是脱离于任何一个进程独立存在的,任何一个进程都可以去访问共享内存,实现进程间的通信。但是为了多进程并发对同一块共享内存的写入操作的安全性,共享内存一般都结合信号量使用来宝成并发安全。
2. 消息队列
3. 信号量
4. 有名管道
5.无名管道
6.信号
7.文件
8.socket
线程间的通信方式上述进程间的方式都可沿用,且还有自己独特的几种:
1.互斥量
2.自旋锁
3.条件变量
4.读写锁
5.线程信号
6.全局变量
进程间采用的通信方式要么需要切换内核上下文,要么要与外设访问(有名管道,文件)。所以速度会比较慢。而线程采用自己特有的通信方式的话,基本都在自己的进程空间内完成,不存在切换,所以通信速度会较快。也就是说,进程间与线程间分别采用的通信方式,除了种类的区别外,还有速度上的区别。另外,进程与线程之间穿插通信的方式,除信号以外其他进程间通信方式都可采用。
线程的实现
线程的本质:https://my.oschina.net/cnyinlinux/blog/367910
进 程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个 进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
这里要进一步总结搞清楚进程和线程的区别,以及他们的关系。
另外我也不知道咋说,就是用户进程、内核进程、用户线程、内核轻量线程,都是些什么关系,而java的多线程是基于linux的KTS轻量级内核实现的,那么这种实现方式的特点以及问题是啥?
首先,进程干的好好的,为啥需要线程。
1. 对于一个应用程序来说,如果一个进程中如果只有一个线程,那么如果这个应用程序(进程)需要读取磁盘、有IO等,那么这个应用程序(进程)就不得不阻塞等待,那整个应用程序就出于暂停状态(这不也是stop the world么)。而且这种情况对cpu也是极大的浪费,因为在等待IO期间,cpu空闲着。
2. 对于第一个问题,那可能有办法解决,那就是一个应用程序也可以多任务的,即使用多进程。当需要IO等比较耗时的操作的时候,交给子进程来完成。思路确实是的,但是
2.1 fork一个子进程消耗是比较大的,也就是说fork()是一个比较昂贵的系统调用,即使在使用了写时复制优化技术后,fork()依然是一个比较昂贵的系统调用。
2.2 各个进程拥有自己独立的地址空间,进程间的协作是比较复杂的,成本也是比较高的,比如共享内存或者消息。但是不管是哪种要么涉及到内核态/用户态的切换、要么就需要通过外设交互,成本都比较大。有的时候使用多进程的机制实现多任务并行计算,进程创建+进程通信的开销好多时候比并行计算生下来的时间还要多。
2.3 多进程之间竞争分时共享cpu,那么在一个进程的cpu时间片用光,切换进程的时候,需要进程切换。这个进程切换的代价是比较大的。对于一个进程来说,每个进程都有自己独立的数据段、代码段、堆栈段,进程的上下文切换的时候,需需保留的线程信息就会比较多
所以多进程实现多任务的一个毛病就是:fork创建进程比较耗时、进程通信比较复杂切昂贵,在进程分时调度中进程分时占用cpu,在切换的时候设计进程上下文切换,这个切换过程也是比较耗时的。
在这种情况下,就有了线程。
线程是一个进程内的可独立调调度执行的单元,一个进程可有有多个线程,多个线程共享进程的资源。那么多线程实现的多任务相比于多进程实现的多任务的优势:
1. fork创建进程是一个比较昂贵的操作;但是clone()创建的线程要轻量很多,因为线程是只有少量的资源(比如运行时调用栈等)
2. 多个线程是共享进程的地址空间,所以线程间的同信就容易得多
3. 一个进程中不同线程切换cpu时间片时,线程上线文的切换,比进程上下文切换耗时要低。一个进程有自己独立的数据段、代码段和堆栈段,而一个线程是没有自己独立的数据段的,所以在上下文切换的时候,保存现场信息就会少一些,数据段其实是大头,堆栈段和代码段占据的空间是相对比较少的。
ps:如果是不同进程之间的线程切换,那么势必也会发生进程上下文切换,这种情况是快不了的。
在不同操作系统中,有着不同的实现方式。根据操作系统内核是否感知可分为:用户线程和内核线程。
- 内核线程:就是操作系统内核实现支持的线程。线程创建、销毁、切换等都是由内核管理完成
- 用户线程:这个是在用户空间,通过函数库实现的线程,线程的创建、销毁、切换都是线程库来实现的。比如linux操作系统的POSIX线程库就实现了用户线程。
从竞争cpu时间片上看:
1. 内核线程是独立去竞争cpu时间片的,因为cpu时间片的调度管理就是内核负责的,所以内核实现线程的时候,争抢时间片就是以线程为单位去争抢。所以内核线程对压榨cpu是更有利的,但是线程的切换都需要切换到内核态才能完成。
2. 用户线程是在用户空间函数库去实现整个线程的管理的,内核都感知不到这些用户线程。那么争抢cpu的时间片,就只能以用户线程所属的进程为单位去争抢,然后进程的多个用户线程再竞争进程获得时间片。一旦这个进程的cpu时间片被用光了,那么这进程中所有的线程都给挂起了。(ps:从这个角度看,也可简单粗暴的理解成一个进程中的多个用户线程会映射到一个内核线程上,只是说实际实现上,用户线程的实现并不依赖于内核是否支持内核线程)
所以用户线程对cpu竞争是不利的,不如内核线程压榨cpu厉害。但是因为是在用户空间完成,所以线程切换是不需要切换到内核态的,所以线程切换就更加轻量。但是用户线程的实现是非常复杂的,因为要自己去考虑线程创建、切换、销毁,以及如何将线程映射到处理器上,是非常复杂的。所以这种纯用户空间的线程,现在很少有使用了。
在使用多任务变成中,可根据实际情况选择使用哪种线程。
除此之外,还有一种进程的实现就是轻量级进程(Light Weight Process:LWP)。LWP可以认为是用户线程和内核线程的一个桥梁,一般没有直接使用内核线程的,而是通过LWP。
一个进程中包含多个LWP,一个LWP和一个内核线程绑定,所以LWP的创建、销毁、切换都设计用户态到内核态的切换。(这个时候的用户线程其实就是个空壳子)
ps:没太明白使用LWP和直接使用KLT的优势是啥?
怎么理解轻量级进程:
1. 首先是轻量级:进程是分配资源的基本单位,进程拥有独立的地址空间,相互不干扰,所以进程之间通信需要借助外力,比如共享内存。但是轻量级进程是没有独立的地址空间,它共享父进程的地址空间,这就是轻量级的含义。
2. 然后是进程:LWP都没有独立的空间为啥叫进程呢?原因就在于竞争cpu上,它和其他进程一起共同竞争cpu,所以在用户空间看来就好像是个进程一样。但本质上,是因为它一对一了一个内核线程,内核线程可以独立的去参与竞争cpu
3. 上面说的用户级线程,是指线程的调度、切换、销毁等都是用户空间通过库函数来实现的,内核完全不感知用户线程的存在。而对于LWP,内核也是不感知其存在的,它也是在用户空间去实现的,所以从这个角度将LWP也是属于用户空间的,只是LWP唯一映射了一个KLT,可以借助内核对KLT的调度来实现LWP的调度,所以是不需要实现LWP的调度的的
4. 轻量级线程和普通的线程是不一样的,它不需要独立分配资源,所以LWP的创建不是通过fork(),而是通过库函数p_thread_create(这是linux操作系统中POSIX线程库中的一个函数),因为LWP要唯一对应一个KLT,所以p_thread_create()可能也调用了clone()这个系统调用,创建一个内核线程。
windows和linux平台上的jvm的线程模型,都是基于LWP这种,但是在Solaris平台上的jvm,因为Solaris除了支持一对一的线程模型,还支持多对多的线程模型,所以solaris版本的jvm提供了参数来选择使用的线程模型。
那么这样是不是可以理解为什么说java的线程在线程切换的时候,需要切换到内核态、需要线程上下文切换、需要进程上线文切换了:
1. java的线程是使用LWP+KLT实现,线程的创建、调度(切换)、销毁都要通过内核来实现,所以需要切换到内核态,才能让内核开始工作完成这些工作。
2. 线程上下文切换是比较好理解的,因为一个线程的时间片用完了,要切换到下一个线程使用cpu,自然要保存当前线程的线程。
3. 进程上下文切换是咋回事呢?这个不是每次线程切换都会发生的,如果是进程内的两个线程发生切换使用cpu,不会发生进程上线文环境的,因为新的线程使用cpu的时候,进程环境没有发生变化;但是如果两个线程属于不同的进程,那么切换的时候就需要保存进程上下文了,那也就是发生进程的上下文切换。
基于用户线程+LWP实现多对对的线程模型
这种用户空间自己实现了用户线程,用户线程的创建和销户由用户空间完成,不需要内核的参与,依然比较轻量。但是用户线程会共用一个LWP池,实现用户线程和LWP的多对多的关系。对于争抢cpu来说,还是以内核线程为准。
java的线程状态以及操作:https://mp.csdn.net/editor/html/103216796