1. 程式人生 > >多執行緒(二)使用多執行緒的準備知識

多執行緒(二)使用多執行緒的準備知識

一、為什麼要使用多執行緒?
        【使計算機所有資源在執行任務的時候能夠全部利用上,以提升計算機資源利用率的方式來提升系統執行效率】
  CPU的單核執行速度由於硬體技術問題已經遇到瓶頸,而概念性的“光腦”貌似離我們還很遙遠,現在的計算機效能提升方向是向多核發展。多核同時工作,協同完成任務。大家熟知的神威·太湖之光超級計算機總共使用了40960顆處理器,總計擁有10649600顆核心、1.31PB記憶體。即使是目前市場上的主流家用電腦也一般達到四核心八執行緒的配置標準。那麼對於這些多核的CPU,在開發軟體的時候就不得不考慮如何充分利用上每一個核的效能,以至於使系統執行的效率更高。

二、CPU是執行的是執行緒任務還是程序任務?


        【CPU執行的是執行緒中定義的任務】
        程序是對記憶體資源的抽象,執行緒是對執行任務的抽象。CPU執行的是執行緒任務,和程序沒有任何關係。所以,CPU中的核在任意時間點只能執行某一個執行緒的任務,具體執行哪個執行緒就要看作業系統的任務排程策略。在單核多執行緒任務中,作業系統會把CPU資源按照時間片劃分,根據執行緒的優先順序選擇執行緒進行執行任務。

三、程式中執行緒的數量控制在核數的1~2倍對嗎?
        【不對!】

         左圖為工作時win10 四核八執行緒 執行緒數: 2094。右圖為閒置centos7  單核 執行緒數 :127 。執行緒數量遠遠超過CPU核心數量的上百倍。

 
        所以在開發程式過程中,如果不是執行緒開啟機制錯誤,就不會產生成上萬級別的執行緒導致執行緒切換浪費資源,而幾百個執行緒切換對CPU來說還不至於產生過多的資源消耗。
             執行緒開啟機制錯誤例子:socket每接收一個請求便建立一個執行緒執行任務。這種情況下,很容易的便能開啟上千上萬個執行緒(在工作中遇到過)。
四、執行緒頻繁切換會耗費資源?


        【消耗資源肯定會,但是消耗的資源一般情況下沒必要重視,當需要去重視的時候你就不會在看本文了】
        執行緒消耗必定會耗費計算機資源,影響系統的執行效率。但是這種級別的資源浪費還沒必要引起關注去考慮優化,隨便優化一個SQL查詢就遠遠比優化執行緒切換效能提升的多。
        
    測試:
        計算任務:計算1億次 f(i)=(i * 10000.56) / 200 (i從1迴圈到10000)  每組資料測試4次。
        電腦配置: 四核八核心CPU 8G記憶體 win10系統(測試過程中執行的有其它程式,但整個過程中沒有新的應用程式開啟或者關閉。測試前後計算機程序數102左右,執行緒數210左右)
        資料如下:單位s
                1執行緒執行10000 * 10000次相同任務運算時間:0.043584183000000006
                1執行緒執行10000 * 10000次相同任務運算時間:0.03411422
                1執行緒執行10000 * 10000次相同任務運算時間:0.033755275
                1執行緒執行10000 * 10000次相同任務運算時間:0.037857329
                
                10執行緒執行10000 * 1000次相同任務運算時間:0.020971566
                10執行緒執行10000 * 1000次相同任務運算時間:0.020400791
                10執行緒執行10000 * 1000次相同任務運算時間:0.043275417000000004
                10執行緒執行10000 * 1000次相同任務運算時間:0.020918537
               
                50執行緒執行10000 * 200次相同任務運算時間:0.041526313
                50執行緒執行10000 * 200次相同任務運算時間:0.028923265
                50執行緒執行10000 * 200次相同任務運算時間:0.020633701000000003
                50執行緒執行10000 * 200次相同任務運算時間:0.01057820000000002

                
                100執行緒執行10000 * 100次相同任務運算時間:0.034126764000000004
                100執行緒執行10000 * 100次相同任務運算時間:0.036614853
                100執行緒執行10000 * 100次相同任務運算時間:0.060594536000000004
                100執行緒執行10000 * 100次相同任務運算時間:0.046860865
                
                500執行緒執行10000 * 20次相同任務運算時間:0.054617363
                500執行緒執行10000 * 20次相同任務運算時間:0.090330437
                500執行緒執行10000 * 20次相同任務運算時間:0.060385270000000005
                500執行緒執行10000 * 20次相同任務運算時間:0.06421362700000001
                
                1000執行緒執行10000 * 10次相同任務運算時間:0.101977442
                1000執行緒執行10000 * 10次相同任務運算時間:0.09040313700000001
                1000執行緒執行10000 * 10次相同任務運算時間:0.094736125
                1000執行緒執行10000 * 10次相同任務運算時間:0.09127384000000001
                
                2000執行緒執行10000 * 5次相同任務運算時間:0.16429143000000002
                2000執行緒執行10000 * 5次相同任務運算時間:0.18807325200000002
                2000執行緒執行10000 * 5次相同任務運算時間:0.16652977000000002
                2000執行緒執行10000 * 5次相同任務運算時間:0.18108197
                
                
                5000執行緒執行10000 * 2次相同任務運算時間:0.44204793600000003
                5000執行緒執行10000 * 2次相同任務運算時間:0.484968116
                5000執行緒執行10000 * 2次相同任務運算時間:0.405066727
                5000執行緒執行10000 * 2次相同任務運算時間:0.42432967400000005
                
                
                10000執行緒執行10000 * 1次相同任務運算時間:1.453686674
                10000執行緒執行10000 * 1次相同任務運算時間:1.307516071
                10000執行緒執行10000 * 1次相同任務運算時間:1.5121169970000001
                10000執行緒執行10000 * 1次相同任務運算時間:1.332141795
                

由資料中可以看出,在程式開啟1-50個執行緒時,執行時間在下降,10-1000個執行緒之內,執行時間都在同一個量級,超過一千執行緒之後,時間提升一個量級。因此,粗略來看 1000個以下執行緒切換並不會浪費大量系統資源。在程式中如果執行緒開啟機制正確,同時使用到執行緒池,那麼系統優化瓶頸就不會線上程切換的問題上。沒必要在這個角度考慮系統優化的問題。
測試程式碼如下:

複製程式碼

 //scala2.12.12 jdk1.8
  def showNCPUTime(): Unit = {
   //測試過程中調整threadNum,inner 這兩個引數,同時保證兩者之積不變 inner * theradNum =10000
  val threadNum = 2000
    val inner = 5
    //固定不變
    val top = 10000    
    val state = new CountDownLatch(threadNum)
    var threads = new Array[Thread](threadNum)
    for (time <- (0 until threadNum)) {
      val thread = new Thread(new Runnable {
        override def run(): Unit = {
          for (i <- (1 to top)) {
            for (j <- (1 to inner)) {
              //i 每次都是從1 到 1000000
              val temp = (i * 105300.56) / 200
            }
          }
          state.countDown()
        }
      })
      threads(time) = thread
    }
    var startTime = System.nanoTime
    for (time <- (0 until threadNum)) {
      threads(time).start()
    }
    state.await()
    val endTime = System.nanoTime
    println(threadNum + "執行緒執行" + top + " * " + inner + "次相同任務運算時間:" + (endTime - startTime) * 0.000000001)
  }

複製程式碼

 
五、使用多執行緒的難點在哪裡?
         多執行緒的難點在於共享資料讀寫順序的問題,保證多個執行緒對同一資料的操作不會產生混亂。
        程式中流動的都是資料,程式影響的也都是資料。在多個執行緒同時訪問共享資料的時候,由於執行緒讀取/寫入的時機不對而導致資料出錯,進而影響業務。

 

下面的問題涉及到作業系統底層知識,列出來只是為了引起讀者的思考,不再回答,以免誤人誤己。
六、對於多核CPU,執行緒任務是如何分配到每個核上面的?
七、單核CPU任務排程策略。在多個執行緒中,作業系統是如何選取其中某個執行緒分配給CPU執行的?選取規則是什麼?
八、執行緒切換耗費資源,那麼都耗費在什麼地方?掛起一個執行緒的時候作業系統都進行了哪些操作?
九、作業系統開啟一個新執行緒需要執行哪些步驟?計算機能夠開啟的最大執行緒數是多少?JVM能夠開啟的最大執行緒數是多少?他們和記憶體的大小有什麼關係?

=============================================

原文連結:多執行緒(二)使用多執行緒的準備知識 轉載請註明出處!

=============================================