【漫畫】揭祕上下文切換

原創:享學課堂講師Peter
轉載請聲明出處!

什麼是上下文切換?

其實在單個處理器的時期,操作系統就能處理多線程併發任務。處理器給每個線程分配 CPU 時間片(Time Slice),線程在分配獲得的時間片內執行任務。

CPU 時間片是 CPU 分配給每個線程執行的時間段,一般爲幾十毫秒。在這麼短的時間內線程互相切換,我們根本感覺不到,所以看上去就好像是同時進行的一樣。

時間片決定了一個線程可以連續佔用處理器運行的時長。當一個線程的時間片用完了,或者因自身原因被迫暫停運行了,這個時候,另外一個線程(可以是同一個線程或者其它進程的線程)就會被操作系統選中,來佔用處理器。這種一個線程被暫停剝奪使用權,另外一個線程被選中開始或者繼續運行的過程就叫做上下文切換(Context Switch)。

多線程上下文切換的原因

上下文切換帶來的性能問題

public class DemoApplication {
       public static void main(String[] args) { 
        //運行多線程 
        MultiThreadTester test1 = new MultiThreadTester();
        test1.Start(); 
        //運行單線程
 SerialTester test2 = new SerialTester(); 
        test2.Start(); 
 }
 
 static class MultiThreadTester extends ThreadContextSwitchTester {
 @Override
         public void Start() { 
         long start = System.currentTimeMillis();
 MyRunnable myRunnable1 = new MyRunnable(); 
       Thread[] threads = new Thread[4];
       //創建多個線程
      for (int i = 0; i < 4; i++) { 
 threads[i] = new Thread(myRunnable1);
      threads[i].start(); 
 }
 for (int i = 0; i < 4; i++) {
      try {
     //等待一起運行完 
     threads[i].join();
 } 
     catch (InterruptedException e){
 e.printStackTrace();
      }
   } 
 long end = System.currentTimeMillis(); 
 System.out.println("多線程運行時間: " + (end - start) + "ms"); 
 System.out.println("計數: " + counter);
     }
     // 創建一個實現Runnable的類
 class MyRunnable implements Runnable { 
    public void run() { while (counter < 100000000) { 
    synchronized (this) { if(counter < 100000000) {
    increaseCounter(); 
                       }
                    }
                 }
              }
          }
     }
 //創建一個單線程
static class SerialTester extends ThreadContextSwitchTester{
 @Override public void Start() { 
 long start = System.currentTimeMillis();
 for (long i = 0; i < count; i++) { increaseCounter(); 
   }
 long end = System.currentTimeMillis(); 
 System.out.println("單線程運行時間: " + (end - start) + "ms");
 System.out.println("計數: " + counter); 
   }
}
 //父類 static abstract c

 lass ThreadContextSwitchTester {
 public static final int count = 100000000;
 public volatile int counter = 0; 
 public int getCount(){
 return this.counter; 
    }
 public void increaseCounter() {
 this.counter += 1;
    } 
 public abstract void Start(); 
    }
 }

執行之後,看一下兩者的時間測試結果:

通過數據對比我們可以看到:串聯的執行速度比並發的執行速度要快。這就是因爲線程的上下文切換導致了額外的開銷,一般來說使用 Synchronized 鎖關鍵字,導致了資源競爭,從而引起了上下文切換,但即使不使用 Synchronized 鎖關鍵字,併發的執行速度也無法超越串聯的執行速度,這是因爲多線程同樣存在着上下文切換。Redis、NodeJS 的設計就很好地體現了單線程串行的優勢。

總結

例如,我們前面講到的 Redis,從內存中快速讀取值,不用考慮 I/O 瓶頸帶來的阻塞問題。而在邏輯相對來說很複雜的場景,等待時間相對較長又或者是需要大量計算的場景,我建議使用多線程來提高系統的整體性能。例如,NIO 時期的文件讀寫操作、圖像處理以及大數據分析等。

喜歡本文的話可以關注我們的官方賬號,第一時間獲取資訊。
你的關注是對我們更新最大的動力哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章