啃碎并发(三):Java线程上下文切换

位流苔原
• 阅读 1330

前言

在过去单CPU时代,单任务在一个时间点只能执行单一程序。之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程。虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个CPU,并交由操作系统来完成多任务间对CPU的运行切换,以使得每个任务都有机会获得一定的时间片运行

再后来发展到多线程技术,使得在一个程序内部能拥有多个线程并行执行。一个线程的执行可以被认为是一个CPU在执行该程序。当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序

多线程比多任务更加有挑战。多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。这可能是在单线程程序中从来不会遇到的问题。其中的一些错误也未必会在单CPU机器上出现,因为两个线程从来不会得到真正的并行执行。然而,更现代的计算机伴随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行

所以,在多线程、多任务情况下,线程上下文切换是必须的,然而对于CPU架构设计中的概念,应先熟悉了解,这样会有助于理解线程上下文切换原理。

1 多核、多CPU、超线程、多线程

1.1 为什么要多核

先要说的是多核、多CPU、超线程,这三个其实都是CPU架构设计的概念,一个现代CPU除了处理器核心之外还包括寄存器、L1L2缓存这些存储设备、浮点运算单元、整数运算单元等一些辅助运算设备以及内部总线等。一个多核的CPU也就是一个CPU上有多个处理器核心,这样有什么好处呢?比如说现在我们要在一台计算机上跑一个多线程的程序,因为是一个进程里的线程,所以需要一些共享一些存储变量,如果这台计算机都是单核单线程CPU的话,就意味着这个程序的不同线程需要经常在CPU之间的外部总线上通信,同时还要处理不同CPU之间不同缓存导致数据不一致的问题,所以在这种场景下多核单CPU的架构就能发挥很大的优势,通信都在内部总线,共用同一个缓存

1.2 为什么要多CPU

前面提了多核的好处,那为什么要多CPU呢?这个其实很容易想到,如果要运行多个程序(进程)的话,假如只有一个CPU的话,就意味着要经常进行进程上下文切换,因为单CPU即便是多核的,也只是多个处理器核心,其他设备都是共用的,所以多个进程就必然要经常进行进程上下文切换,这个代价是很高的

1.3 为什么要超线程

超线程这个概念是Intel提出的,简单来说是在一个CPU上真正的并发两个线程,听起来似乎不太可能,因为CPU都是分时的啊,其实这里也是分时,因为前面也提到一个CPU除了处理器核心还有其他设备,一段代码执行过程也不光是只有处理器核心工作,如果两个线程A和B,A正在使用处理器核心,B正在使用缓存或者其他设备,那AB两个线程就可以并发执行,但是如果AB都在访问同一个设备,那就只能等前一个线程执行完后一个线程才能执行。实现这种并发的原理是在CPU里加了一个协调辅助核心,根据Intel提供的数据,这样一个设备会使得设备面积增大5%,但是性能提高15%~30%。

1.4 为什么要多线程

这个问题也许是面试中问的最多的一个经典问题了,一个进程里多线程之间可以共享变量,线程间通信开销也较小,可以更好的利用多核CPU的性能,多核CPU上跑多线程程序往往会比单线程更快,有的时候甚至在单核CPU上多线程程序也会有更好的性能,因为虽然多线程会有上下文切换和线程创建销毁开销,但是单线程程序会被IO阻塞无法充分利用CPU资源,加上线程的上下文开销较低以及线程池的大量应用,多线程在很多场景下都会有更高的效率

1.5 线程与进程

进程是操作系统的管理单位,而线程则是进程的管理单位;一个进程至少包含一个执行线程。不管是在单线程还是多线程中,每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。虽然线程寄生在进程中,但与他的进程是不同的概念,并且可以分别处理:进程是系统分配资源的基本单位,线程是调度CPU的基本单位

一个线程指的是进程中一个单一顺序的控制流,一个进程中可以并行多个线程,每条线程并行执行不同的任务。每个线程共享堆空间,拥有自己独立的栈空间

啃碎并发(三):Java线程上下文切换

              进程&线程表

2 上下文切换

支持多任务处理是CPU设计史上最大的跨越之一。在计算机中,多任务处理是指同时运行两个或多个程序。从使用者的角度来看,这看起来并不复杂或者难以实现,但是它确实是计算机设计史上一次大的飞跃。在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。上下文切换就是这样一个过程,允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。

多任务系统往往需要同时执行多道作业。作业数往往大于机器的CPU数,然而一颗CPU同时只能执行一项任务,如何让用户感觉这些任务正在同时进行呢? 操作系统的设计者巧妙地利用了时间片轮转的方式, CPU给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务任务的状态保存及再加载, 这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗CPU上执行变成了可能。

啃碎并发(三):Java线程上下文切换

任务的状态保存及再加载, 这段过程就叫做上下文切换

2.1 基本概念

上下文切换(有时也称做进程切换或任务切换)是指CPU从一个进程或线程切换到另一个进程或线程。

啃碎并发(三):Java线程上下文切换

上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动:

啃碎并发(三):Java线程上下文切换

2.2 切换种类

上下文切换在不同的场合有不同的含义,在下表中列出:

啃碎并发(三):Java线程上下文切换

2.3 切换步骤

在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB, process control block)中的。PCB还经常被称作“切换桢”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用

PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息,它使一个在多道程序环境下不能独立运行的程序成为一个能独立运行的基本单位或一个能与其他进程并发执行的进程。

啃碎并发(三):Java线程上下文切换

线程切换和进程切换的步骤也不同。进程的上下文切换分为两步:

啃碎并发(三):Java线程上下文切换

对于Linux来说,线程和进程的最大区别就在于地址空间。对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。所以明显是进程切换代价大。线程上下文切换和进程上下文切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出

对于一个正在执行的进程包括程序计数器、寄存器、变量的当前值等,而这些数据都是保存在CPU的寄存器中的,且这些寄存器只能是正在使用CPU的进程才能享用在进程切换时,首先得保存上一个进程的这些数据(便于下次获得CPU的使用权时从上次的中断处开始继续顺序执行,而不是返回到进程开始,否则每次进程重新获得CPU时所处理的任务都是上一次的重复,可能永远也到不了进程的结束出,因为一个进程几乎不可能执行完所有任务后才释放CPU),然后将本次获得CPU的进程的这些数据装入CPU的寄存器从上次断点处继续执行剩下的任务

操作系统为了便于管理系统内部进程,为每个进程创建了一张进程表项:

啃碎并发(三):Java线程上下文切换

进程表项

2.4 切换查看

在Linux系统下可以使用vmstat命令来查看上下文切换的次数,下面是利用vmstat查看上下文切换次数的示例:

啃碎并发(三):Java线程上下文切换

上线文切换查看

vmstat 1指每秒统计一次,其中cs列就是指上下文切换的数目. 一般情况下, 空闲系统的上下文切换每秒大概在1500以下.

3 切换原因

引起线程上下文切换的原因,主要存在三种情况如下:

啃碎并发(三):Java线程上下文切换

对于我们经常使用的抢占式操作系统而言,引起线程上下文切换的原因大概有以下几种:

啃碎并发(三):Java线程上下文切换

4 切换损耗

上下文切换会带来直接和间接两种因素影响程序性能的消耗。

啃碎并发(三):Java线程上下文切换

5 减少切换

既然上下文切换会导致额外的开销,因此减少上下文切换次数便可以提高多线程程序的运行效率。但上下文切换又分为2种:

啃碎并发(三):Java线程上下文切换

所以,减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程

啃碎并发(三):Java线程上下文切换

6 线程数目

合理设置线程数目,关键点是:1. 尽量减少线程切换和管理的开支;2. 最大化利用CPU

啃碎并发(三):Java线程上下文切换

所以对于任务耗时短的情况,要求线程尽量少,如果线程太多,有可能出现线程切换和管理的时间,大于任务执行的时间,那效率就低了;

对于耗时长的任务,要分是CPU任务,还是IO等类型的任务。如果是CPU类型的任务,线程数不宜太多;但是如果是IO类型的任务,线程多一些更好,可以更充分利用CPU。

高并发,低耗时的情况:建议少线程,只要满足并发即可,因为上下文切换本来就多,并且高并发就意味着CPU是处于繁忙状态的, 增加更多地线程也不会让线程得到执行时间片,反而会增加线程切换的开销;例如并发100,线程池可能设置为10就可以;

低并发,高耗时的情况:建议多线程,保证有空闲线程,接受新的任务;例如并发10,线程池可能就要设置为20;

高并发高耗时:1. 要分析任务类型;2. 增加排队;3. 加大线程数

点赞
收藏
评论区
推荐文章
好买-葡萄 好买-葡萄
4年前
Linux操作系统
什么是Linux操作系统Linux操作系统(GNU/Linux)是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX的多用户、多任务、支持多线程和多CPU的操作系统。特点基本思想:一切都是文件;每个文件都有确定的用途完全免费完全兼容POSIX1.0标准多用户、多任务良好的界面支持多平台优点Linux由众多微内核组成,其源码完全开源,集全球程序员
Wesley13 Wesley13
4年前
java多线程总结
线程或者说多线程,是我们处理多任务的强大工具。线程和进程是不同的,每个进程都是一个独立运行的程序,拥有自己的变量,且不同进程间的变量不能共享;而线程是运行在进程内部的,每个正在运行的进程至少有一个线程,而且不同的线程之间可以在进程范围内共享数据。也就是说进程有自己独立的存储空间,而线程是和它所属的进程内的其他线程共享一个存储空间。线程的使用可以使我们能够并行
Python的多任务编程
前言Python程序代码都是按自上而下的顺序加载并执行的,但实际需要代码处理的任务并不都是需要按部就班的顺序执行,通常为提高代码执行的效率,需要多个代码执行任务同时执行,也就是多任务编程的需求。基本的计算机模型是由CPU、RAM及各种资源(键盘、硬盘、显卡、网卡等)组成,代码的执行过程,实际就是CPU和相关寄存器及RAM之间的相关处理的过程。在单核CPU场景
Wesley13 Wesley13
4年前
Java并发和多线程那些事儿
我记得我接触电脑的时候是在小学三年级的时候,那是1995年,那年发布了windows95,但是我学习的时候还是只是dos系统,简单对于文件的一些命令操作还有五笔在过去的那个年代,电脑都是单CPU,也就是单任务处理;多任务处理到后来才慢慢发展起来,多任务代表电脑在同一时刻内可以处理很多并行操作,这样CPU被利用率高了,多个任务都可以共享。多任务处理的出现对
Wesley13 Wesley13
4年前
Java 多线程(一)—— 概念的引入
并发和并行并行:指两个或多个时间在同一时刻发生(同时发生);并发:指两个或多个事件在一个时间段内发生。  在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单CPU系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替
Stella981 Stella981
4年前
Python进程、线程、协程的对比
1\.执行过程每个线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在进程中,由进程提供多个线程执行控制。每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。协程,又称微线程,Coroutine。执行过程中,在子程序内部可中断,然后转而
Wesley13 Wesley13
4年前
JAVA多线程的学习笔记
第一次在开源中国上发博文,简单的把以前在达内培训JAVA时的一篇日志复制共享一下。多进程(多任务)-针对OS多线程(Thread)-一个进程内部的多任务java语言(JVM)支持多线程编程java.lang.ThreadThread类由JDK提供,用于封装对JVM中线程的控制,调用C语言的类库实现。1、创
Wesley13 Wesley13
4年前
Java总结:Java多线程
多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。Java给多线程编程提供了内置的支持。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。这里定义和线程相关的另一个术语进程:一个进程包括由操作系统分配的内存空间,
Wesley13 Wesley13
4年前
CPU调度
1.CPU调度程序  每当CPU空闲时,OS必须从就绪队列选择一个进程来执行。进程选择由短期调度程序或CPU调度程序执行。调度程序从内存中选择一个能执行的进程,并为之分配CPU。2.抢占:可以选择       (1)当一个进程从运行状态切换到就绪状态;(eg:当出现中断时)       (2)当一个进
Wesley13 Wesley13
4年前
Java多线程(一)、理解进程与多线程的概念及关系
一、什么是进程?简单理解,在多任务系统中,每个独立运行的程序就是一个进程,也可以理解为当前正在运行的每个程序都是一个进程。我们现在使用的操作系统大都是多任务系统的,如:Windows、Linux、MacOSX、Unix等。因为单个CPU在同一时刻只能执行一个程序,这是铁律。但在系统中单个CPU又怎么能同时执行多个程序呢?实际情况这是由操作系
Wesley13 Wesley13
4年前
Java并发编程基础
什么情况下应该使用多线程:线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止CPU占用的情况,也就是说会使得CPU闲置。还有一些场景就是比如对于一个函数中的运算逻辑的性能问题,我们可以通过多线程的技术,使得一个函数中的多