线程上下文切换扫盲文

1、什么是上下文

首先,需要讲清楚什么是上下文。

每个任务运行前,CPU 都需要知道任务从哪里加载、又从哪里开始运行,这就涉及到 CPU 寄存器 和 程序计数器(PC):

【1】CPU 寄存器是 CPU 内置的容量小、但速度极快的内存;

【2】程序计数器会存储 CPU 正在执行的指令位置,或者即将执行的指令位置。

这两个是 CPU 运行任何任务前都必须依赖的环境,因此叫做 CPU 上下文。

2、什么是上下文切换

       上下文切换就是把前一个任务的CPU上下文保存起来,然后加载新任务的上下文到这些指令寄存器(IR)和程序寄存器(PC)等寄存器中。这些被保存下来的上下文会存储在操作系统的内核中,等待任务重新调度执行时再次加载进来,这样就能保证任务的原来状态不受影响,让任务看起来是连续运行的。

      这就像我们同时读两本书,当我们在读一本英文的技术书时,发现某个单词不认识,于是便打开中英文字典,但是在放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。

3、什么时候会发生上下文切换?

按导致上下文切换的因素划分,可将上下文切换分为两点:

(1)自发性上下文切换

自发性上下文切换指线程由于自身因素导致的切出。通过调用下列方法会导致自发性上下文切换:

Thread.sleep()

Object.wait()

Thread.yeild()

Thread.join()

LockSupport.park()

 

(2)非自发性上下文切换

非自发性上下文切换指线程由于线程调度器的原因被迫切出。发生下列情况可能导致非自发性上下文切换:

【1】切出线程的时间片用完

【2】有一个比切出线程优先级更高的线程需要被运行

【3】虚拟机的垃圾回收动作

 

4、上下文切换的开销

上下文切换的开销包括直接开销和间接开销。

直接开销有如下几点:

【1】操作系统保存回复上下文所需的开销

【2】线程调度器调度线程的开销

间接开销有如下几点:

【1】处理器高速缓存重新加载的开销

【2】上下文切换可能导致整个一级高速缓存中的内容被冲刷,即被写入到下一级高速缓存或主存

 

5、查看系统的上下文切换情况

既然过多的上下文切换会把CPU的时间消耗在上下文环境的保存上,并没有充分利用其计算功能。那就需要查看当前系统的上下文切换情况了。

vmstat

查看系统的上下文切换情况

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 2453028   2108 1211540    0    0     3    10   27   35  0  0 99  0  0
 0  0      0 2453004   2108 1211572    0    0     0     0   52   66  0  0 100  0  0
  • cs (centext switch) 每秒的上下文切换次数
  • in (interrupt) 每秒的中断次数
  • r (Runing or Runnable) 就绪队列的长度,也就是正在运行和等待CPU的进程数。
  • b (Blocked) 不可中断睡眠状态的进程数

pidstat

pidstat 可以看到具体的某个应用程序的上下文切换情况。

 

$ pidstat -w 1
Linux 3.10.0-957.el7.x86_64 (localhost.localdomain)     2019年03月23日     _x86_64_    (2 CPU)

13时51分31秒   UID       PID   cswch/s nvcswch/s  Command
13时51分32秒     0         9      9.80      0.00  rcu_sched
13时51分32秒     0        11      0.98      0.00  watchdog/0
13时51分32秒     0        12      0.98      0.00  watchdog/1
13时51分32秒     0       481      0.98      0.00  kworker/1:3
13时51分32秒     0      4683     18.63      0.00  xfsaild/dm-0
13时51分32秒     0      9433      9.80      0.00  vmtoolsd
13时51分32秒     0     27261      0.98      0.00  kworker/u256:0
13时51分32秒     0     40878      2.94      0.00  kworker/0:1
13时51分32秒     0     40880      0.98      0.00  pidstat
  • cswch (voluntary context switches) 自愿上下文切换,指的是进程无法获得所需的资源导致的上下文切换。比如I/O不足,内存不足。
  • nvcswch (non voluntary context switches) 非自愿上下文切换,指的是 进程由于时间片已到等原因,被系统强制调度,进而发生上下文切换。比如大量进程在争抢CPU

模拟上下文切换的场景

这里使用的sysbench,模拟操作系统的多线程调度瓶颈。以10个线程运行5分钟的基准测试。模拟多线程切换

$  sysbench --threads=100 --max-time=300 threads run

观察vmstat的输出

 $ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
17  0      0 2442576   2108 1217720    0    0     3    10   27   80  0  0 99  0  0
11  0      0 2442576   2108 1217720    0    0     0     0 23882 1325686 10 89  1  0  0
10  0      0 2442576   2108 1217720    0    0     0     0 22815 1348568 11 89  0  0  0
17  0      0 2442576   2108 1217720    0    0     0     0 23978 1328481 10 89  1  0  0
11  0      0 2442576   2108 1217720    0    0     0    25 23081 1344558 11 89  0  0  0
14  0      0 2442576   2108 1217720    0    0     0     0 23804 1305193 11 89  1  0  0
19  0      0 2442576   2108 1217720    0    0     0     0 22418 1254798 11 89  0  0  0

可观察到 cs 瞬间上升(第一行是开机以来的参数的平均值)。
接下来观察pidstat ,这里需要加上 -t 参数,显示线程,要不不带参数,看不到sysbench。

$ pidstat -u -w 1
平均时间:   UID       PID    %usr %system  %guest    %CPU   CPU  Command
平均时间:     0     42213    0.00    0.05    0.00    0.05     -  kworker/0:0
平均时间:     0     42218   19.82  100.00    0.00  100.00     -  sysbench
平均时间:     0     42321    0.33    1.00    0.00    1.33     -  pidstat

平均时间:   UID       PID   cswch/s nvcswch/s  Command
平均时间:     0         2      0.05      0.00  kthreadd
平均时间:     0         3      0.29      0.00  ksoftirqd/0
平均时间:     0         9      8.32      0.00  rcu_sched
平均时间:     0        11      0.24      0.00  watchdog/0
平均时间:     0        12      0.24      0.00  watchdog/1
平均时间:     0        14      0.90      0.00  ksoftirqd/1
平均时间:     0        37      0.10      0.00  khugepaged

加上-t后
可以看到 sysbench 发生了大量的自愿上下文切换

$ pidstat -wut 1
平均时间:     0     27261         -      1.85      0.00  kworker/u256:0
平均时间:     0         -     27261      1.85      0.00  |__kworker/u256:0
平均时间:   997         -     27300      1.85      0.00  |__grafana-server
平均时间:   997         -     27304      0.93      0.00  |__grafana-server
平均时间:   997         -     27307      1.85      0.00  |__grafana-server
平均时间:   997         -     27341      1.85      0.00  |__grafana-server
平均时间:     0         -     42219   2879.63  11057.41  |__sysbench
平均时间:     0         -     42220   1902.78  13955.56  |__sysbench
平均时间:     0         -     42221   4470.37  11027.78  |__sysbench
平均时间:     0         -     42222   1792.59  17249.07  |__sysbench
平均时间:     0         -     42223   2542.59   7395.37  |__sysbench
平均时间:     0         -     42224   1183.33  16775.93  |__sysbench
平均时间:     0         -     42225   3678.70  12963.89  |__sysbench
平均时间:     0         -     42226   3208.33  15801.85  |__sysbench
平均时间:     0         -     42227   2602.78  13896.30  |__sysbench

通过dstat命令可以看到,除了有很多的资源上下文切换,还有很多中断。

$ dstat
You did not select any stats, using -cdngy by default.
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
  1   2  97   0   0   0|5683B   19k|   0     0 |   0     0 | 583    30k
  8  85   7   0   0   0|   0     0 | 132B 1060B|   0     0 |  28k  977k
  8  82  10   0   0   0|   0     0 | 132B  620B|   0     0 |  25k 1025k

这是重调度中断(RES),在唤醒空闲状态的CPU来重新调度新任务运行。具体可看 /proc/interrupts。

小结

一般上下文切换在数百到一万之内上下文切换超过1万,很可能遇到性能问题。需要具体看看了。

  • 资源上下文切换时说明进程在等待资源,有可能发生了I/O等问题;
  • 非自愿上下文切换,说明进程在被强制调度,也就是在争抢CPU;
  • 中断次数多了,说明CPU在被中断处理程序占用。可以通过/proc/interrupts 查看。

 

参考:

《Java并发编程的艺术》

《Java多线程编程实战指南》

《Linux性能优化实战》

https://www.jianshu.com/p/1c80cd47e8cf

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

架构帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值