单核CPU是否支持多线程
此前多线程专栏聊过,多线程执行会比单线程执行更快,能提高并发速度。
但是并不是启动越多的线程,并发就越高,线程的创建、销毁、上下文切换频繁,也会有很大的开销,反而你的程序的处理能力就会下降。
# 超线程
现代CPU除了处理器核心之外还包括寄存器、L1L2缓存这些存储设备、浮点运算单元、整数运算单元等一些辅助运算设备以及内部总线等。一个多核的CPU也就是一个CPU上有多个处理器核心,就意味着程序的不同线程需要经常在CPU之间的外部总线上通信,同时还要处理不同CPU之间不同缓存导致数据不一致的问题。
超线程这个概念是Intel提出的,简单来说是在一个CPU上真正的并发两个线程,由于CPU都是分时的(如果两个线程A和B,A正在使用处理器核心,B正在使用缓存或者其他设备,那AB两个线程就可以并发执行,但是如果AB都在访问同一个设备,那就只能等前一个线程执行完后一个线程才能执行)。实现这种并发的原理是 在CPU里加了一个协调辅助核心,根据Intel提供的数据,这样一个设备会使得设备面积增大5%,但是性能提高15%~30%。
# 时间片
CPU 的核心在同一个时间内,只会执行单一线程。
但是现在的系统都是多任务系统,需要同时执行多道作业,作业数往往大于机器的CPU数。
例如 windows ,你同时着 微信、文件夹、输入法、浏览器、typora、git,来回切换,看着就像是同时进行的,但是CPU的核心却只有几个。
这就是 时间轮询片,简称 时间片 的原理。
时间片 是CPU分配 给线程执行的时间。
# 上下文切换
切换分多种情况:
- 线程切换,同一进程中的两个线程之间的切换
- 进程切换,两个进程之间的切换
- 模式切换,在给定线程中,用户模式和内核模式的切换
- 地址空间切换,将虚拟内存切换到物理内存
假如是单核CPU的情况下,当微信拿到了时间片,便执行;如果浏览器此时需要执行,CPU就会调度,进行 线程切换。
CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一任务的状态并执行。任务的状态保存及再加载,这段过程就叫做上下文切换。
那是如何保存当前状态的呢?
每个线程都有一个程序计数器(记录要执行的下一条指令),一组寄存器(保存当前线程的工作变量),堆栈(记录执行历史,其中每一帧保存了一个已经调用但未返回的过程)。
- 寄存器 是 CPU 内部的数量较少但是速度很快的内存(与之对应的是 CPU 外部相对较慢的 RAM 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
我们常说的 内存、主存,都是只 RAM内存条的内存
- 程序计数器是一个专用的寄存器,用于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。
- 挂起当前任务(线程/进程),将这个任务在 CPU 中的状态(上下文)存储于内存中的某处
- 恢复一个任务(线程/进程),在内存中检索下一个任务的上下文并将其在 CPU 的寄存器中恢复
- 跳转到程序计数器所指向的位置(即跳转到任务被中断时的代码行),以恢复该进程在程序中]
线程上下文切换会有什么问题呢?
上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。
- 直接消耗:指的是CPU寄存器需要保存和加载, 系统调度器的代码需要执行, TLB实例需要重新加载, CPU 的pipeline需要刷掉
- 间接消耗:指的是多核的cache之间得共享数据, 间接消耗对于程序的影响要看线程工作区操作数据的大小
# 引起线程上下文切换的因素
- 当前执行任务(线程)的时间片用完之后,系统CPU正常调度下一个任务
- 中断处理,在中断处理中,其他程序”打断”了当前正在运行的程序。当CPU接收到中断请求时,会在正在运行的程序和发起中断请求的程序之间进行一次上下文切换。中断分为硬件中断和软件中断,软件中断包括因为IO阻塞、未抢到资源或者用户代码等原因,线程被挂起。
- 用户态切换,对于一些操作系统,当进行用户态切换时也会进行一次上下文切换,虽然这不是必须的。
- 多个任务抢占锁资源,在多任务处理中,CPU会在不同程序之间来回切换,每个程序都有相应的处理时间片,CPU在两个时间片的间隔中进行上下文切换
# 因此优化手段有:
- 无锁并发编程,多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
- CAS算法,Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程
- 协程,单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
合理设置线程数目既可以最大化利用CPU,又可以减少线程切换的开销。
- 高并发,低耗时的情况,建议少线程。
- 低并发,高耗时的情况:建议多线程。
- 高并发高耗时,要分析任务类型、增加排队、加大线程数
# 线程调度
# 抢占式调度
指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
java使用的线程调使用抢占式调度,Java中线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。