多线程系列:一介绍

本文是从结合并发编程网的学习笔记,主要是关注java的多线程及并发性。

优点:

  • 资源利用率更好
  • 程序响应更快
可以理解为更充分利用多核CPU的资源,去编写高性能的程序。

缺点:

设计更复杂
可以这样理解,要真确处理多线程直接数据同步的问题。
上下文切换的开销

当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)简称CS。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。

这里原文写的比较简单,我觉得有必要补充下下文切换的几个概念:
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。
上下文切换在多任务操作系统中是一个必须的特性。多任务操作系统是指多个进程运行在一个 CPU 中互不打扰,看起来像同时运行一样。这个并行的错觉是由于上下文在高速的切换(每秒几十上百次)。
当某一进程自愿放弃它的 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

你可能感兴趣的:(多线程系列:一介绍)