2013-02-24 83 views
25

對於像計數器這樣簡單的事情,如果多個線程將增加數量。我讀到互斥鎖可以降低效率,因爲線程必須等待。所以,對我來說,原子計數器會是最有效的,但我在內部讀取它基本上是一個鎖?所以我想我很困惑如何能比其他更有效率。哪個更高效,基本的互斥鎖或原子整數?

+0

此答案適用於支持pthread或某些子集的所有平臺和編程語言嗎?我並不完全理解pthread,操作系統和編程語言之間的關係,但似乎這些關係可能是相關的。 – 2017-09-16 16:37:14

回答

13

原子操作槓桿處理器的支持(比較和交換指令),並且不使用鎖可言,而鎖都更依賴於操作系統和執行有所不同,對於例如,Win和Linux。

鎖實際上會掛起線程執行,爲其他任務釋放cpu資源,但在停止/重新啓動線程時會產生明顯的上下文切換開銷。 相反,嘗試進行原子操作的線程不會等待,直到成功(嘗試繁忙等待)纔會繼續嘗試,因此它們不會在上下文切換開銷中發生,但不會釋放cpu資源。

總結一下,如果線程之間的爭用足夠低,原子操作通常會更快。你應該做基準測試,因爲沒有其他可靠的方法可以知道上下文切換和繁忙等待之間的最低開銷。

16

如果您有一個支持哪些原子操作的計數器,它將比互斥鎖更有效。

從技術上講,原子將鎖定大多數平臺上的存儲器總線。但是,有兩個改進的細節:

  • 在內存總線鎖定期間掛起線程是不可能的,但可以在互斥鎖期間暫掛線程。這是什麼讓你獲得無鎖保證(它沒有說任何關於不鎖定 - 它只是保證至少有一個線程進展)。
  • Mutexes最終最終被原子實現。由於您至少需要一個原子操作來鎖定互斥鎖,並且需要一個原子操作來解鎖互斥鎖,因此即使在最好的情況下,鎖定互斥鎖也需要至少兩次。
+0

重要的是要理解它取決於編譯器或解釋器如何支持平臺爲平臺生成最佳機器指令(在這種情況下爲無鎖指令)。我認爲這是@Cort Ammon所說的「支持」。另外一些互斥體可能會保證一些或所有線程的前向進展或公平性,這些線程並非由簡單的原子指令構成。 – 2017-09-16 16:28:08

1

原子整數爲用戶模式對象那裏它的更有效的比運行在內核模式互斥。原子整數的範圍是單個應用程序,而互斥體的範圍適用於機器上所有正在運行的軟件。

+0

這幾乎是事實。像Linux的互斥體這樣的現代互斥體實現傾向於利用原子操作來避免在快速路徑上切換到內核模式。如果原子操作無法執行所需的任務(例如線程需要阻塞的情況),這種互斥鎖只需跳入內核模式。 – 2017-09-20 22:14:10

1

Mutex是一種內核級語義,即使在Process level處也提供相互排斥。請注意,它可以有助於擴展跨進程邊界的互斥,而不僅限於進程內部(線程)。這是昂貴的。

原子計數器,AtomicInteger例如基於CAS,並且通常嘗試嘗試操作直到成功。基本上,在這種情況下,線程競爭或增加\以原子方式遞減值。在這裏,你可能會看到一個線程正在嘗試使用當前值來使用好的CPU週期。

由於您要維護計數器,因此AtomicInteger \ AtomicLong將成爲您的最佳使用案例。

1

大多數處理器支持原子讀取或寫入,並且通常交換原子。這意味着處理器本身在單個操作中寫入或讀取最新值,並且與正常整數訪問相比可能會丟失幾個週期,特別是因爲編譯器無法在原子操作周圍優化幾乎正常。

另一方面,互斥鎖是進入和離開的許多代碼行,並且在執行期間,訪問相同位置的其他處理器完全停滯,這顯然是一個很大的開銷。在未優化的高級代碼中,互斥輸入/輸出和原子將是函數調用,但對於互斥鎖,任何競爭對手的處理器都將在您的互斥鎖輸入函數返回時以及退出函數啓動時被鎖定。對於原子,它只是被鎖定的實際操作的持續時間。優化應該降低成本,但不是全部。

如果你想增加,那麼你的現代處理器可能支持原子增量/減量,這將是偉大的。

如果不是,則使用處理器原子cmp &交換或使用互斥鎖來實現。

互斥:

get the lock 
read 
increment 
write 
release the lock 

原子CMP &交換:

atomic read the value 
calc the increment 
do{ 
    atomic cmpswap value, increment 
    recalc the increment 
}while the cmp&swap did not see the expected value 

所以這第二個版本有一個循環[櫃面另一個處理器增加我們的原子操作之間的值,因此值不再匹配,如果有很多競爭者,它會變長],但通常應該比互斥體版本更快,但互斥體版本可能允許該處理器進行任務切換。

3

一個最小的(符合標準的)互斥的實現需要2個基本要素:

  • 原子地傳達線程之間的狀態變化的方式(在「鎖定」狀態)
  • 記憶障礙,執行內存操作保護由互斥體保持在保護區內。

由於C++標準需要'同步'關係,所以沒有辦法讓它更簡單。

一個最小的(正確)的實現可能是這樣的:

class mutex { 
    std::atomic<bool> flag{false}; 

public: 
    void lock() 
    { 
     while (flag.exchange(true, std::memory_order_relaxed)); 
     std::atomic_thread_fence(std::memory_order_acquire); 
    } 

    void unlock() 
    { 
     std::atomic_thread_fence(std::memory_order_release); 
     flag.store(false, std::memory_order_relaxed); 
    } 
}; 

由於其簡單性(它不能暫停執行的線程),它是可能的,低競爭下,這種實現優於一個std::mutex 。 但即便如此,很容易地看到,每個整數增量,通過這種互斥的保護,需要執行以下操作:

  • atomic商店釋放互斥鎖
  • atomic比較並交換(讀-modify寫)獲得互斥體(可能多次)
  • 整數增量

如果你比較,與該遞增與單(無條件獨立std::atomic<int>)閱讀修改 - 寫入(例如。 fetch_add), 有理由期望原子操作(使用相同的排序模型)將優於使用互斥量的情況。