線程上下文切換的性能損耗到底有多少,一直沒有直觀的理解,今天寫個程序測試一下。先看看下面的程序(點擊下載):html
ThreadTester是全部Tester的基類。全部的Tester都乾的是一樣一件事情,把counter增長到100000000,每次只能加1。java
1: public abstract class ThreadTester
2: {
3: public const long MAX_COUNTER_NUMBER = 100000000;
4:
5: private long _counter = 0;
6:
7: //得到計數
8: public virtual long GetCounter()
9: {
10: return this._counter;
11: }
12:
13: //增長計數器
14: protected virtual void IncreaseCounter()
15: {
16: this._counter += 1;
17: }
18:
19: //啓動測試
20: public abstract void Start();
21:
22: //得到Counter從開始增長到如今的數字所耗的時間
23: public abstract long GetElapsedMillisecondsOfIncreaseCounter();
24:
25: //測試是否正在運行
26: public abstract bool IsTesterRunning();
27: }
SingleThreadTester是單線程計數。redis
1: class SingleThreadTester : ThreadTester
2: {
3: private Stopwatch _aStopWatch = new Stopwatch();
4:
5: public override void Start()
6: {
7: _aStopWatch.Start();
8:
9: Thread aThread = new Thread(() => WorkInThread());
10: aThread.Start();
11: }
12:
13: public override long GetElapsedMillisecondsOfIncreaseCounter()
14: {
15: return this._aStopWatch.ElapsedMilliseconds;
16: }
17:
18: public override bool IsTesterRunning()
19: {
20: return _aStopWatch.IsRunning;
21: }
22:
23: private void WorkInThread()
24: {
25: while (true)
26: {
27: if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
28: {
29: _aStopWatch.Stop();
30: break;
31: }
32:
33: this.IncreaseCounter();
34: }
35: }
36: }
TwoThreadSwitchTester是兩個線程交替計數。多線程
1: class TwoThreadSwitchTester : ThreadTester
2: {
3: private Stopwatch _aStopWatch = new Stopwatch();
4: private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
5:
6: public override void Start()
7: {
8: _aStopWatch.Start();
9:
10: Thread aThread1 = new Thread(() => Work1InThread());
11: aThread1.Start();
12:
13: Thread aThread2 = new Thread(() => Work2InThread());
14: aThread2.Start();
15: }
16:
17: public override long GetElapsedMillisecondsOfIncreaseCounter()
18: {
19: return this._aStopWatch.ElapsedMilliseconds;
20: }
21:
22: public override bool IsTesterRunning()
23: {
24: return _aStopWatch.IsRunning;
25: }
26:
27: private void Work1InThread()
28: {
29: while (true)
30: {
31: _autoResetEvent.WaitOne();
32:
33: this.IncreaseCounter();
34:
35: if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
36: {
37: _aStopWatch.Stop();
38: break;
39: }
40:
41: _autoResetEvent.Set();
42: }
43: }
44:
45: private void Work2InThread()
46: {
47: while (true)
48: {
49: _autoResetEvent.Set();
50: _autoResetEvent.WaitOne();
51: this.IncreaseCounter();
52:
53: if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
54: {
55: _aStopWatch.Stop();
56: break;
57: }
58: }
59: }
60: }
MultiThreadTester能夠指定線程數,多個線程爭搶計數。併發
1: class MultiThreadTester : ThreadTester
2: {
3: private Stopwatch _aStopWatch = new Stopwatch();
4: private readonly int _threadCount = 0;
5: private readonly object _counterLock = new object();
6:
7: public MultiThreadTester(int threadCount)
8: {
9: this._threadCount = threadCount;
10: }
11:
12: public override void Start()
13: {
14: _aStopWatch.Start();
15:
16: for (int i = 0; i < _threadCount; i++)
17: {
18: Thread aThread = new Thread(() => WorkInThread());
19: aThread.Start();
20: }
21: }
22:
23: public override long GetElapsedMillisecondsOfIncreaseCounter()
24: {
25: return this._aStopWatch.ElapsedMilliseconds;
26: }
27:
28: public override bool IsTesterRunning()
29: {
30: return _aStopWatch.IsRunning;
31: }
32:
33: private void WorkInThread()
34: {
35: while (true)
36: {
37: lock (_counterLock)
38: {
39: if (this.GetCounter() > ThreadTester.MAX_COUNTER_NUMBER)
40: {
41: _aStopWatch.Stop();
42: break;
43: }
44:
45: this.IncreaseCounter();
46: }
47: }
48: }
49: }
Program的Main函數中,根據用戶的選擇來決定執行哪一個測試類。app
1: class Program
2: {
3: static void Main(string[] args)
4: {
5:
6: string inputText = GetUserChoice();
7:
8: while (!"4".Equals(inputText))
9: {
10: ThreadTester tester = GreateThreadTesterByInputText(inputText);
11: tester.Start();
12:
13: while (true)
14: {
15: Console.WriteLine(GetStatusOfThreadTester(tester));
16: if (!tester.IsTesterRunning())
17: {
18: break;
19: }
20: Thread.Sleep(100);
21: }
22:
23: inputText = GetUserChoice();
24: }
25:
26: Console.Write("Click enter to exit...");
27: }
28:
29: private static string GetStatusOfThreadTester(ThreadTester tester)
30: {
31: return string.Format("[耗時{0}ms] counter = {1}, {2}",
32: tester.GetElapsedMillisecondsOfIncreaseCounter(), tester.GetCounter(),
33: tester.IsTesterRunning() ? "running" : "stopped");
34: }
35:
36: private static ThreadTester GreateThreadTesterByInputText(string inputText)
37: {
38: switch (inputText)
39: {
40: case "1":
41: return new SingleThreadTester();
42: case "2":
43: return new TwoThreadSwitchTester();
44: default:
45: return new MultiThreadTester(100);
46: }
47: }
48:
49: private static string GetUserChoice()
50: {
51: Console.WriteLine(@"==Please select the option in the following list:==
52: 1. SingleThreadTester
53: 2. TwoThreadSwitchTester
54: 3. MultiThreadTester
55: 4. Exit");
56:
57: string inputText = Console.ReadLine();
58:
59: return inputText;
60: }
61: }
三個測試類,運行結果以下:ide
Single Thread:
[耗時407ms] counter = 100000001, stopped
[耗時453ms] counter = 100000001, stopped
[耗時412ms] counter = 100000001, stopped函數
Two Thread Switch:
[耗時161503ms] counter = 100000001, stopped
[耗時164508ms] counter = 100000001, stopped
[耗時164201ms] counter = 100000001, stopped高併發
Multi Threads - 100 Threads:
[耗時3659ms] counter = 100000001, stopped
[耗時3950ms] counter = 100000001, stopped
[耗時3720ms] counter = 100000001, stopped工具
Multi Threads - 2 Threads:
[耗時3078ms] counter = 100000001, stopped
[耗時3160ms] counter = 100000001, stopped
[耗時3106ms] counter = 100000001, stopped
上下文切換的精肯定義能夠參考: http://www.linfo.org/context_switch.html。多任務系統每每須要同時執行多道做業。做業數每每大於機器的CPU數,然而一顆CPU同時只能執行一項任務,爲了讓用戶感受這些任務正在同時進行,操做系統的設計者巧妙地利用了時間片輪轉的方式,CPU給每一個任務都服務必定的時間,而後把當前任務的狀態保存下來,在加載下一任務的狀態後,繼續服務下一任務。任務的狀態保存及再加載,這段過程就叫作上下文切換。時間片輪轉的方式使多個任務在同一顆CPU上執行變成了可能,但同時也帶來了保存現場和加載現場的直接消耗。(Note. 更精確地說, 上下文切換會帶來直接和間接兩種因素影響程序性能的消耗. 直接消耗包括: CPU寄存器須要保存和加載, 系統調度器的代碼須要執行, TLB實例須要從新加載, CPU 的pipeline須要刷掉; 間接消耗指的是多核的cache之間得共享數據, 間接消耗對於程序的影響要看線程工做區操做數據的大小).
根據上面上下文切換的定義,咱們作出下面的假設:
因爲Windows下沒有像Linux下的vmstat這樣的工具,這裏咱們使用Process Explorer看看程序執行的時候線程上線文切換的次數。
Single Thread:
計數期間,線程總共切換了580-548=32次。(548是啓動程序後,初始的數值)
Two Thread Switch:
計數期間,線程總共切換了33673295-124=33673171次。(124是啓動程序後,初始的數值)
Multi Threads - 100 Threads:
計數期間,線程總共切換了846-329=517次。(329是啓動程序後,初始的數值)
Multi Threads - 2 Threads:
計數期間,線程總共切換了295-201=94次。(201是啓動程序後,初始的數值)
從上面收集的數據來看,和咱們的判斷基本相符。
再想一想原來學過的知識,以前一直覺得線程多幹活就快,簡直是把學過的計算機原理都還給老師了。真正幹活的不是線程,而是CPU。線程越多,幹活不必定越快。
那麼高併發的狀況下何時適合單線程,何時適合多線程呢?
適合單線程的場景:單個線程的工做邏輯簡單,並且速度很是快,好比從內存中讀取某個值,或者從Hash表根據key得到某個value。Redis和Node.js這類程序都是單線程,適合單個線程簡單快速的場景。
適合多線程的場景:單個線程的工做邏輯複雜,等待時間較長或者須要消耗大量系統運算資源,好比須要從多個遠程服務得到數據並計算,或者圖像處理。
例子程序:http://pan.baidu.com/s/1ntNUPWP