本文是从结合并发编程网的学习笔记,主要是关注java的多线程及并发性。
当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)简称CS。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
这里原文写的比较简单,我觉得有必要补充下下文切换的几个概念:*) context是指CPU寄存器和程序计数器在任何时间点的内容
*)CS可以描述为kernel执行下面的操作
1. 挂起一个进程,并储存该进程当时在内存中所反映出的状态
2. 从内存中恢复下一个要执行的进程,恢复该进程原来的状态到寄存器,返回到其上次暂停的执行代码然后继续执行
*)CS只能发生在内核态(kernel mode):内核态是 CPU 的一种有特权的模式,在这种模式下只有内核运行并且可以访问所有内存和其他系统资源。其他的程序,如应用程序,在最开始都是运行在用户态,但是他们能通过系统调用来运行部分内核的代码
*)system call会陷入内核态,是user mode => kernel mode的过程,我们称之为模式切换(mode switch),但不表明会发生CS(其实mode switch同样也会做很多和CS一样的流程,例如通过寄存器传递user mode 和 kernel mode之间的一些参数)
*)一个硬件中断的产生,也可能导致kernel收到signal后进行CS。这里的硬件中断是指硬件设备(如键盘、鼠标、调试解调器、系统时钟)给内核发送的一个信号,该信号表示一个事件(如按键、鼠标移动、从网络连接接收到数据)发生了。 目前主流CPU 都支持硬件上下文切换。然而,大多数现代的操作系统通过软件实现上下文切换,而非使用硬件上下文切换,这样能够运行在任何 CPU 上。同时,使用软件上下文切换可以尝试获得更好的性能。软件的上下文切换最先在 Linux 2.4 中实现。
vmstat直接运行即可,在最后几列,有CPU的context switch次数。 这个是系统层面的,加入想看特定进程的情况,可以使用pidstat。
1
2
3
4
5
6
7
|
$ vmstat
1
100
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
0
0
88
233484
288756
1784744
0
0
0
23
0
0
4
1
94
0
0
4
0
88
233236
288756
1784752
0
0
0
0
6202
7880
4
1
96
0
0
2
0
88
233360
288756
1784800
0
0
0
112
6277
7612
4
1
95
0
0
0
0
88
232864
288756
1784804
0
0
0
644
5747
6593
6
0
92
2
0
|
执行pidstat,将输出系统启动后所有活动进程的cpu统计信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
linux:~ # pidstat
Linux
2.6
.
32.12
-0.7
-
default
(linux)
06
/
18
/
12
_x
86
_
64
_
11:
37:
19
PID %usr %system %guest %CPU CPU Command
……
11:
37:
19
11452
0.00
0.00
0.00
0.00
2
bash
11:
37:
19
11509
0.00
0.00
0.00
0.00
3
dd
11:
37:
19:
pidstat获取信息时间点
PID: 进程pid
%usr: 进程在用户态运行所占cpu时间比率
%system: 进程在内核态运行所占cpu时间比率
%CPU: 进程运行所占cpu时间比率
CPU: 指示进程在哪个核运行
Command: 拉起进程对应的命令
备注:执行pidstat默认输出信息为系统启动后到执行时间点的统计信息,因而即使当前某进程的cpu占用率很高,输出中的值有可能仍为
0
。
|
上下文切换的性能消耗在哪里呢?
context switch过高,会导致CPU像个搬运工,频繁在寄存器和运行队列直接奔波 ,更多的时间花在了线程切换,而不是真正工作的线程上。直接的消耗包括CPU寄存器需要保存和加载,系统调度器的代码需要执行。间接消耗在于多核cache之间的共享数据。
引起上下文切换的原因有哪些?
对于抢占式操作系统而言, 大体有几种:
1、当前任务的时间片用完之后,系统CPU正常调度下一个任务;
2、当前任务碰到IO阻塞,调度线程将挂起此任务,继续下一个任务;
3、多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下一个任务;
4、用户代码挂起当前任务,让出CPU时间;
5、硬件中断;
工具:待补充
参考:
http://ifeve.com/java-concurrency-thread-directory/
http://ifeve.com/context-switch-definition/
http://iamzhongyong.iteye.com/blog/1895728