多线程上下文切换

前言算法

本文来自方腾飞老师《Java并发编程的艺术》第一章。编程

并发编程的目的是为了让程序运行得更快,可是并非启动更多的线程就能让程序最大限度地并发执行。在进行并发编程时,若是但愿经过多线程执行任务让程序运行得更快,会面临很是多的挑战,好比上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制问题,本文要研究的是上下文切换的问题。多线程

 

什么是上下文切换并发

即便是单核CPU也支持多线程执行代码,CPU经过给每一个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,由于时间片很是短,因此CPU经过不停地切换线程执行,让咱们感受多个线程时同时执行的,时间片通常是几十毫秒(ms)。工具

CPU经过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。可是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,能够再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换性能

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

 

上下文切换代码测试spa

下面的代码演示串行和并发执行并累加操做的时间:操作系统

 1 public class ContextSwitchTest  2 {  3     private static final long count = 10000;  4     
 5     public static void main(String[] args) throws Exception  6  {  7  concurrency();  8  serial();  9  } 10     
11     private static void concurrency() throws Exception 12  { 13         long start = System.currentTimeMillis(); 14         Thread thread = new Thread(new Runnable(){ 15             public void run() 16  { 17                 int a = 0; 18                 for (int i = 0; i < count; i++) 19  { 20                     a += 5; 21  } 22  } 23  }); 24  thread.start(); 25         int b = 0; 26         for (long i = 0; i < count; i++) 27  { 28             b --; 29  } 30  thread.join(); 31         long time = System.currentTimeMillis() - start; 32         System.out.println("Concurrency:" + time + "ms, b = " + b); 33  } 34     
35     private static void serial() 36  { 37         long start = System.currentTimeMillis(); 38         int a = 0; 39         for (long i = 0; i < count; i++) 40  { 41             a += 5; 42  } 43         int b = 0; 44         for (int i = 0; i < count; i++) 45  { 46             b --; 47  } 48         long time = System.currentTimeMillis() - start; 49         System.out.println("Serial:" + time + "ms, b = " + b + ", a = " + a); 50  } 51 }

修改上面的count值,即修改循环次数,看一下串行运行和并发运行的时间测试结果:线程

循环次数 串行执行耗时/ms 并发执行耗时/ms 串行和并发对比
1亿 78 50 并发快约0.5倍
1000万 10 6 并发快约0.5~1倍
100万 3 2 差很少
10万 2 2 差很少
1万 0 1 差很少,十几回执行下来,整体而言串行略快

从表中能够看出,100次并发执行累加如下,串行执行和并发执行的运行速度整体而言差很少,1万次如下串行执行甚至还能够说是略快。为何并发执行的速度会比串行慢呢?这就是由于线程有建立和上下文切换的开销

 

引发线程上下文切换的缘由

对于咱们常用的抢占式操做系统而言,引发线程上下文切换的缘由大概有如下几种:

  1. 当前执行任务的时间片用完以后,系统CPU正常调度下一个任务
  2. 当前执行任务碰到IO阻塞,调度器将此任务挂起,继续下一任务
  3. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务
  4. 用户代码挂起当前任务,让出CPU时间
  5. 硬件中断

 

上下文切换次数查看

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

CS(Context Switch)表示上下文切换的次数,从图中能够看到,上下文每秒钟切换500~600次左右。

若是要查看上下文切换的时长,能够利用Lmbench3,这是一个性能分析工具。

 

如何减小上下文切换

既然上下文切换会致使额外的开销,所以减小上下文切换次数即可以提升多线程程序的运行效率。减小上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。

  • 无锁并发编程。多线程竞争时,会引发上下文切换,因此多线程处理数据时,能够用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不一样的线程处理不一样段的数据
  • CAS算法。Java的Atomic包使用CAS算法来更新数据,而不须要加锁
  • 使用最少线程。避免建立不须要的线程,好比任务不多,可是建立了不少线程来处理,这样会形成大量线程都处于等待状态
  • 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
相关文章
相关标签/搜索