Mutex and Spinlock
理論如此,那麼實際呢。。再次做一個實驗。
實驗設置如下:
建立兩個鎖類,一個利用C++的atomic flag實現了Spinlock,另一個封裝了std::mutex,代碼如下:
class spinlock {
11 std::atomic_flag lock_;
12 public:
13 spinlock() {
14 lock_.clear();
15 }
17 inline void lock() {
18 while (lock_.test_and_set(std::memory_order_acquire));
19 }
20
21 inline void unlock() {
22 lock_.clear(std::memory_order_release);
23 }
};
31 class Mutexlock {
32 private:
33 std::mutex lock_;
34
35 public:
36 inline void lock() {
37 lock_.lock();
38 }
39
40 inline void unlock() {
41 lock_.unlock();
42 }
};
實驗中,線程數可調,每個線程重複進行10000次取鎖->將線程自己的一個整數加一(線程不共享操作的整數,也就是在這個整數上沒有緩存競爭)->釋放鎖。這種設計可以模擬出大量爭奪鎖的輕負荷線程的場景。
實驗1
i7 2.5GHz 4核,6MB緩存,超線程。實驗數據如下:
線程數 mutex時間 spinlock時間 (時間單位為微秒,也即是千分之一秒)
mutex spinlock(ms) 1 2 1
mutex spinlock(ms) 2 294 9
mutex spinlock(ms) 4 284 45
mutex spinlock(ms) 6 1970 111
mutex spinlock(ms) 8 2606 153
mutex spinlock(ms) 10 3320 229
mutex spinlock(ms) 12 3898 335
mutex spinlock(ms) 14 4669 368
mutex spinlock(ms) 16 5304 530
mutex spinlock(ms) 18 5938 758
mutex spinlock(ms) 20 6608 845
mutex spinlock(ms) 22 7313 889
mutex spinlock(ms) 24 7935 1021
mutex spinlock(ms) 26 8952 929
mutex spinlock(ms) 28 9495 1393
mutex spinlock(ms) 30 10532 1563
mutex spinlock(ms) 32 10833 2040
實驗結果比較令人滿意。mutex比spinlock慢5到10倍。
實驗2
亞馬遜EC2虛擬機,32個虛擬核,底層硬體為Xeon E5-2680 v2。這款CPU為10核, 2.8GHz,25MB Cache,超線程。合理推測32個虛擬核(16個物理核)至少利用了2個以上的實際CPU,也就是多個socket。
線程數 mutex時間 spinlock時間 (時間單位為毫秒)
mutex spinlock(ms) 1 3 3
mutex spinlock(ms) 2 16 5
mutex spinlock(ms) 4 51 68
mutex spinlock(ms) 6 84 85
mutex spinlock(ms) 8 134 202
mutex spinlock(ms) 10 138 299
mutex spinlock(ms) 12 199 452
mutex spinlock(ms) 14 255 551
mutex spinlock(ms) 16 231 594
mutex spinlock(ms) 18 298 830
mutex spinlock(ms) 20 279 912
mutex spinlock(ms) 22 334 1000
mutex spinlock(ms) 24 345 1172
mutex spinlock(ms) 26 383 1282
mutex spinlock(ms) 28 412 1389
mutex spinlock(ms) 30 444 1544
mutex spinlock(ms) 32 467 1711
實驗結果令人大跌眼鏡,線程數越多,mutex的性能越好。即便是線程數很少,spinlock性能也不佔優很多。
好吧,為什麼實驗結果差異這麼大?我這裡有多個猜想,但是現在我還沒有辦法驗證。
猜想1:i7的實驗環境實在筆記本上跑的,只有一個處理器,而亞馬遜的32虛擬機里肯定有超過一個處理器,這麼來看,有一個問題就是緩存競爭。由於Spinlock里,多線程共享了Spinlock,它們其實也面臨著緩存競爭的問題,某一個線程對Spinlock的獲取會造成其它線程對該鎖的緩存失效,再加上忙等待對鎖的反覆檢查,實際上這些線程在緩存里對某一個緩存行拚命進行競爭,這會造成巨大的成本。雪上加霜的是,跨處理器(Non Uniform Memory Access)的通訊本身就慢。在單處理器上的多線程結果相對好一點,原因是i7的核數少,能同時執行的線程不多,緩存競爭交情,代價較小。
猜想2:i7的實驗使用的是mac系統,32核環境是ubuntu,有可能linux平台對std::mutex進行過優化。
經驗教訓:
1. 並行編程很難,沒有一個萬能的解決方法,理論上有效的,實際根據情況也會失效。
2. 純靠Spinlock進行優化也不行,要盡量減少共享的數據結構,共享一個Spinlock這個想法本身就不靠譜。在實際使用時,最好fine-grain locking,例如對10個數據結構,每一個都一把鎖,降低線程對鎖的競爭。
推薦閱讀:
TAG:並發 |