一、為什么要使用多線程?
【使計算機所有資源在執行任務的時候能夠全部利用上,以提升計算機資源利用率的方式來提升系統執行效率】
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能夠開啟的最大線程數是多少?他們和內存的大小有什么關系?
=============================================
原文鏈接:多線程(二)使用多線程的准備知識 轉載請注明出處!
=============================================
-----end