每個程序員都知道,多線程能提高應用吞吐量和處理速度。但不是每個程序員都知道爲什麼?
CPU運行時,通過將於運行時間分片,通過調度來分配給各個進程線程來執行。因爲時間片非常短,所以常常讓人誤以爲是多個線程是同時並行執行。
使用多線程來提高程序處理速度,其本質是提高對CPU的利用率。主要是兩個方面
- 柱塞等待時充分利用CPU
當程序發生阻塞的操作時候,例如IO等待,CPU將就空閒下來了。而使用多線程,當一些線程發生阻塞的時候,另一些線程則仍能利用CPU,而不至於讓CPU一直空閒。 - 利用CPU的多核並行計算能力
現在的CPU基本上都是多核的。使用多線程,可以利用多核同時執行多個線程,而不至於單線程時一個核心滿載,而其他核心空閒。
多線程就一定能提高處理速度嗎?顯示着不一定。當程序偏計算型的時候,盲目啓動大量線程來併發,並不能提高處理速度,反而會降低處理速度。因爲在多個線程進行切換執行的時候會帶會一定的開銷。其中有 上下文切換開銷,CPU調度線程的開銷,線程創建和消亡的開銷等。其中主要是上下文切換帶來的開銷。
上下文切換的開銷,主要是來自於當線程切換時保存上一個線程現場和載入下一個線程現場的操作。
使用空循環來模擬計算性任務,看下在不同數量的線程,程序的表現
import java.util.ArrayList;
import java.util.List;
public class CounterDemo {
private static final long num = 1000000000L;
public static void splitCount(int threadNum) {
for (long i = 0; i < num/threadNum; i++) {}
}
public static long getIntervalTimeToNow(long startTime) {
return System.currentTimeMillis() - startTime;
}
public static void main(String[] args) throws InterruptedException {
countWithMultithread(1);
countWithMultithread(10);
countWithMultithread(100);
countWithMultithread(1000);
countWithMultithread(10000);
}
private static void countWithMultithread(final int threadNum) throws InterruptedException {
long startTime;
Runnable splitCount = new Runnable() {
@Override
public void run() {
CounterDemo.splitCount(threadNum);
}
};
List<Thread> list = new ArrayList<>();
for (int i = 0; i < threadNum; i++) {
Thread thread1 = new Thread(splitCount);
list.add(thread1);
}
startTime = System.currentTimeMillis();
for (Thread th: list) {
th.start();
}
for (Thread th: list) {
th.join();
}
System.out.println(String.format("%1$9d", threadNum) + " thread need:"+String.format("%1$6d",getIntervalTimeToNow(startTime)));
}
}
輸出結果
1 thread need: 409
10 thread need: 92
100 thread need: 140
1000 thread need: 226
10000 thread need: 978
100000 thread need: 10059
執行的時候,使用vmstat 查看 CS (context switch)切換次數
vmstat 1
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 2701584 151472 9676072 0 0 0 60 393 419 0 0 100 0 0
0 0 0 2701708 151472 9676084 0 0 0 0 751 751 1 0 99 0 0
0 0 0 2701708 151472 9676092 0 0 0 0 354 384 0 0 100 0 0
0 0 0 2701708 151472 9676096 0 0 0 0 435 464 0 0 100 0 0
0 0 0 2701708 151472 9676108 0 0 0 72 439 491 0 0 100 0 0
2 0 0 2701708 151472 9676128 0 0 0 32 381 453 0 0 100 0 0
1 0 0 2680344 151472 9676168 0 0 0 4 6077 4869 21 1 78 0 0
4 0 0 2664740 151472 9676180 0 0 0 0 126656 149528 17 11 72 0 0
3 0 0 2564376 151472 9676188 0 0 0 0 107107 138418 12 11 77 0 0
3 0 0 2565556 151472 9676196 0 0 0 0 128143 166234 7 14 79 0 0
4 0 0 2563056 151472 9676204 0 0 0 64 125162 163707 7 13 79 0 0
3 0 0 2567808 151472 9676208 0 0 0 0 136266 180092 7 13 80 0 0
2 0 0 2566560 151472 9676216 0 0 0 0 117768 154666 7 14 79 0 0
1 0 0 2568276 151472 9676220 0 0 0 0 107585 139240 8 13 79 0 0
當上下文切換最多的時候每秒切換了18W+次。從輸入結果來看,線程1K,10K的時候,多線程並沒有打來處理效率的提升,反而下降了。
引起上下文切換的原因有哪些?主要有以下幾種:
- 當前任務的時間片用完之後,系統CPU正常調度下一個任務;
- 當前任務碰到IO阻塞,調度線程將掛起此任務,繼續下一個任務;
- 多個任務搶佔鎖資源,當前任務沒有搶到,被調度器掛起,繼續下一個任務;
- 用戶代碼掛起當前任務,讓出CPU時間;
- 硬件中斷;