多線程上下文切換

前言算法

本文來自方騰飛老師《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算法來更新數據,而不須要加鎖
  • 使用最少線程。避免建立不須要的線程,好比任務不多,可是建立了不少線程來處理,這樣會形成大量線程都處於等待狀態
  • 協程。在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換
相關文章
相關標籤/搜索