標籤:

如何理解 C++11 的六種 memory order?

看了好多資料,還是感覺雲里霧裡


從一個程序員角度的 Take away:

雖然是六種類型,但是理解了四種同步的情形基本就差不多了。

1. Relaxed ordering: 在單個線程內,所有原子操作是順序進行的。按照什麼順序?基本上就是代碼順序(sequenced-before)。這就是唯一的限制了!兩個來自不同線程的原子操作是什麼順序?兩個字:任意。

2. Release -- acquire: 來自不同線程的兩個原子操作順序不一定?那怎麼能限制一下它們的順序?這就需要兩個線程進行一下同步(synchronize-with)。同步什麼呢?同步對一個變數的讀寫操作。線程 A 原子性地把值寫入 x (release), 然後線程 B 原子性地讀取 x 的值(acquire). 這樣線程 B 保證讀取到 x 的最新值。注意 release -- acquire 有個牛逼的副作用:線程 A 中所有發生在 release x 之前的寫操作,對在線程 B acquire x 之後的任何讀操作都可見!本來 A, B 間讀寫操作順序不定。這麼一同步,在 x 這個點前後, A, B 線程之間有了個順序關係,稱作 inter-thread happens-before.

3. Release -- consume: 我去,我只想同步一個 x 的讀寫操作,結果把 release 之前的寫操作都順帶同步了?如果我想避免這個額外開銷怎麼辦?用 release -- consume 唄。同步還是一樣的同步,這回副作用弱了點:在線程 B acquire x 之後的讀操作中,有一些是依賴於 x 的值的讀操作。管這些依賴於 x 的讀操作叫 賴B讀. 同理在線程 A 裡面, release x 也有一些它所依賴的其他寫操作,這些寫操作自然發生在 release x 之前了。管這些寫操作叫 賴A寫. 現在這個副作用就是,只有 賴B讀 能看見 賴A寫. (卧槽真累)

有人問了,說什麼叫數據依賴(carries dependency)?其實這玩意兒巨簡單:

S1. c = a + b;
S2. e = c + d;

S2 數據依賴於 S1,因為它需要 c 的值。

4. Sequential consistency: 理解了前面的幾個,順序一致性就最好理解了。Release -- acquire 就同步一個 x,順序一致就是對所有的變數的所有原子操作都同步。這麼一來,我擦,所有的原子操作就跟由一個線程順序執行似的。


評論里有很多關於x86內存模型的指正,放在這裡:

Loads are not reordered with other loads.Stores are not reordered with other stores.Stores are not reordered with older loads.

然後最重要的:

Loads may be reordered with older stores to different locations.

因為 store-load 可以被重排,所以x86不是順序一致。但是因為其他三種讀寫順序不能被重排,所以x86是 acquire/release 語義。

aquire語義:load 之後的讀寫操作無法被重排至 load 之前。即 load-load, load-store 不能被重排。

release語義:store 之前的讀寫操作無法被重排至 store 之後。即 load-store, store-store 不能被重排。


最簡單的試試 relaxed ordering 的方法就是拿出手機。寫個小程序,故意留個 race condition,然後放到 iPhone 或者安卓手機上調,不用 release -- acquire 保准出錯。然而這種 bug 你在 x86 的 host 機上是調不出來的,即便拿模擬器也調不出來


在此翻譯和總結了3篇本引用文獻中的部分內容,拋磚引玉。下文大致分三部分:

    • 先介紹了內存系統coherence和consistency的行為表現, 並指出達成coherent的三個條件。
    • 其次,將內存一致性模型分為順序一致性模型(sequential consistency model)和鬆弛一致性模型(relaxed consistency model), 並討論這兩種模型的概念、實現與代價;
    • 最後,討論了原子操作間的關係(synchronized-with, happens-before等),淺析C++11內存模型,將其提供的6種內存次序歸類成三種內存模型。

I. Memory coherence and memory consistency

[1] Chapter 5.2介紹了由共享內存(shared memory)的多核計算機因為使用緩存(cache)而引入的緩存連貫性(?)問題(cache coherence problem)。考慮下圖:

圖1. 針對單一內存地址的緩存一致性問題(引自[1] Figure 5.3)

圖1描述的問題是:

    1. 處理器 A 緩存(讀) X 的值 1;
    2. 處理器 B 緩存(讀) X 的值 1;
    3. 處理器 A 直寫(write through) X 的值為 0, 導致處理器 B 看到的(緩存中的) X 的值仍為舊值 1.

通俗地說,我們可以說一個內存系統是為Coherent, 如果任何一個對數據的讀操作總是返回其最近被改寫的值。這個模糊且過分簡單的定義主要包含了內存系統兩方面的行為表現:

    • Coherence定義了一個讀操作能獲得什麼樣的值。
    • Consitency定義了何時一個寫操作的值會被讀操作獲得。

[1]中稱,滿足如下三個條件的內存系統是為coherent

    • 1. 對於同一地址 X, 若處理器 P 在寫後讀,並且在 P 的寫後沒有別的處理器寫 X,那麼 P 的讀總是能返回 P 寫入的值。
      • 條件1要求系統提供我們在單處理器中已熟知的程序次序(program order)。
    • 2. 處理器 P1 完成對 X 的寫後,P2 讀 X. 如果兩者之間相隔時間足夠長,並且沒有其他處理器寫 X, 那麼 P2 可以獲得 P1 寫入的值。
      • 條件2要求寫結果最終對別的處理器可見。
    • 3. 對同一地址的寫操作是串列的(serialized), 在所有處理器看來,通過不同處理器對X的寫操作都是以相同次序進行的。舉個例子,如果值 1 先被寫入,值 2 後被寫入,那麼處理器不能先讀到值 2, 然後再讀到值 1.
      • 性質3要求系統提供寫串列化(write serialization). 若這一性質不被滿足,程序錯誤可能會被引發:考慮 P1 先寫 P2 後寫的情況,如果存在一些處理器先看到P2的寫結果,後看到 P1 的值,那麼這個處理器將無限期地保持P1寫入的值。

雖然上述性質可以充分保證系統coherence的達成,但它仍然未對寫操作何時對讀操作可見進行描述:考慮P1先寫P2後讀的情況,如果寫讀操作間隔很短,P2可能無法獲得P1寫入的值。

被引用超過一千多次,同時被[1][3] highly recommend 的[2]作為了解memory consistency model的入門導引,正是定義了寫操作的結果何時被讀操作獲得。

II. Memory consistency model

本質上,內存一致模型限制了讀操作的返回值。

2.1 Sequential consistency model

2.1.1 什麼是sequential consistency

直觀上,讀操作應該返回「最後」一次寫入的值。

    • 在單處理器系統中,「最後」由程序次序定義。
    • 在多處理器系統中,我們稱之為順序連貫(sequential consistency, SC).

通俗地說,SC要求所有內存操作表現為(appear)逐個執行(任一次的執行結果都像是所有處理器的操作都以某種次序執行),每個處理器中的操作都以其程序指定的次序執行。SC有兩點要求

    1. 在每個處理器內,維護每個處理器的程序次序;
    2. 在所有處理器間,維護單一的表徵所有操作的次序。對於寫操作W1, W2, 不能出現從處理器 P1 看來,執行次序為 W1-&>W2; 從處理器 P2 看來,執行次序卻為 W2-&>W1 這種情況。
      • 這使得內存操作需要表現為原子執行(瞬發執行):可以想像系統由單一的全局內存組成,每一時刻,由switch將內存連向任意的處理器,每個處理器按程序順序發射(issue)內存操作。這樣,switch就可以提供全局的內存串列化性質。

考慮下面程序:

圖2. SC的兩個要求(引自[2] Figure 2. Examples for sequential consistency)

圖 2a 闡述了 SC 對程序次序的要求(要求一)。當處理器 P1 想要進入臨界區時,

它先將 Flag1 更新為 1, 再去判斷 P2 是否嘗試進入臨界區(Flag2). 若 Flag2 為 0,
表示 P2 未嘗試進入臨界區,此時 P1 可以安全進入臨界區。

    • 這個演算法假設如果 P1 中讀到 Flag2 等於0, 那麼P1的寫操作(Flag1=1)會在P2
      的寫操作和讀操作之前發生,這可以避免 P2 也進入臨界區。SC 通過維護程序次序來保證這一點。

圖 2b 闡述了原子性要求。原子性允許我們假設 P1 對 A 的寫操作對整個系統(P2, P3) 是同時可見的:

    1. P1 將 A 寫成1;
    2. P2 先看到 P1 寫 A 後才寫 B;
    3. P3 看到 P2 寫 B 後才去讀 A, P3 此時讀出的 A 必須要是 1 (而不會是0)
    4. 因為從 P2 看來操作執行次序為 (A=1)-&>(B=1), 不允許P3在看到操作 B=1 時,沒有看到 A=1.

2.1.2 實現Sequential consistency

為實現SC,要考慮它與常見硬體優化間的衝突。

2.1.2.1 在無緩存的體系結構下實現SC

對於無緩存的體系結構,[2]以三個常見硬體優化為例,討論了SC與它們間的衝突及解決辦法:

    • 帶有讀旁路的寫緩衝(Write buffers with read bypassing)
      • 讀操作可以不等待寫操作,導致後續的讀操作越過前面的寫操作,違反程序次序
    • 重疊寫(Overlapping writes)
      • 對於不同地址的多個寫操作同時進行,導致後續的寫操作越過前面的讀操作,違反程序次序
    • 非阻塞讀(Nonblocking reads)
      • 多個讀操作同時進行,導致後續的讀操作越過前面的讀操作先執行,違反程序次序

為解決上述硬體優化對SC帶來的問題,[2]提出了程序次序要求(program order requirement): 按照程序次序,處理器的上一次內存操作需要在下一次內存操作前完成。 ([2]中針對每個衝突提出了特定的解決方法,在此不展開)

2.1.2.2 在有緩存的體系結構下實現SC

對於帶有緩存的體系結構,這種數據的副本(緩存)的出現引入了三個額外的問題:

    • 緩存一致性協議(cache coherence protocols)
      • [2]指出cache coherence的定義是
        • 一個寫操作最終要對所有處理器可見([1]對cache coherence的條件1,2的結合)
        • 對同一地址的寫操作串列化([1]對cache coherence的條件3)
      • cache coherence的定義不能推出SC(不充分):SC要求對所有地址的寫操作串列化。因此我們並不用cache coherence定義SC, 它僅作為一種傳遞新值(newly written value)的機制。
    • 檢查寫完成(detecting write completion)
      • 假設圖3 中的處理器帶有直寫緩存(write through cache),P2 緩存了 Data.

        圖3. 違反SC的直寫緩存(引自[2] Figure 3b)
      • 考慮如下執行次序:
        1. P1 先完成了 Data 在內存上的寫操作;
        2. P1 沒有等待 Data 的寫結果傳播到 P2 的緩存中,繼續進行 Head 的寫操作;
        3. P2 讀取到了內存中 Head 的新值;
        4. P2 繼續執行,讀到了緩存中 Data 的舊值。
      • 違反SC,因此我們需要延後每個處理器發布寫確認通知的時間:直至別的處理器發回寫確認通知,才發射下一個寫操作。
    • 維護寫原子性(maintaining write atomicity): 「寫原子性」是我們在2.1.2中談到的SC的兩個要求之一。「將值的改變傳播給多個處理器的緩存」這一操作是非原子操作(非瞬發完成的),因此需要採取特殊措施提供寫原子性的假象。因此我們提出兩個要求,這兩個要求將共同保證寫原子性的實現。
      • 要求1:針對同一地址的寫操作被串列化(serialized). 圖4闡述了對這個條件的需求:如果對 A 的寫操作不是序列化的,那麼 P3 和 P4 輸出(寄存器
        1,2)的結果將會不同,這違反了次序一致性。這種情況可以在基於網路(而不是匯流排)的系統中產生,由於消息可經不同路徑傳遞,這種系統不 供它們傳遞次序的保證。

        圖4. 引自[2] Figure 4. Example for serialization of writes.

      • 要求2:對一個新寫的值的讀操作,必須要等待所有(別的)緩存對該寫操作都返回確認通知後才進行。考慮圖2b中的程序
        1. P2 讀 A 為 1
        2. 「P2 對 B 的更新」先於「P1 對 A 的更新」到達 P3
        3. P3 獲得 B 的新值,獲得 A 的舊值
        • 這使得 P2 和 P3 看到的對值 A, B 的寫操作次序不同,違反的了寫原子性要求

2.1.3 實現SC的總結

注意到,與特定硬體優化,如重疊寫(overlapping writes)、緩存(cache)等技術相結合時,要保證這種SC是有代價的。[1] Chapter 5.6給出了為保證SC, 一個寫操作的耗時從 50 cycles 變為 170 cycles 的例子。同時,SC的要求同樣會限制編譯器優化。

2.2 Relaxed consistency model

為獲得好的性能,我們可以引入放鬆內存一致性模型(relaxed memory consistency models),這些模型主要通過兩種方式優化程序(讀寫):

    • 放鬆對程序次序的要求:這种放松與此前所述的「在無緩存的體系結構中採用的優化」類似,僅適用於對不同地址的操作對(opeartion pairs)間使用。
    • 放鬆對寫原子性的要求:一些模型允許讀操作在「一個寫操作未對所有處理器可見」前返回值。這种放松僅適用於基於緩存的體系結構。

我們用A-&>B表示 A 先於 B 完成,[1] Chapter 5.6 根據放鬆的操作對次序,給出了多種內存模型:

    • 放鬆 W-&>R的模型, 稱為total store ordering或processor consistency. 這種序列模型仍保持了寫(store)操作間的次序;
    • 放鬆 W-&>W的模型, 稱為partial store ordering
    • 放鬆 R-&>W 和 R-&>R的模型有很多種:
      • Weak ordering
      • Power PC consistency model
      • Release consistency

通過放鬆這些次序,處理器可能獲得顯著的性能提升。

[2] 針對這些模型的複雜性(放鬆不同操作對的優勢和複雜性,一個寫操作何時才算完成,以及決定處理器何時能看到它自己修改的值等)做了相對詳盡的描述,在此不展開。

III. C++11 memory order

[3] Chapter 5.3 Synchronizing operations and enforcing order介紹了下述內存關係與內存次序。

3.1 Synchronized-with 與 happens-before 關係

考慮下述程序:

圖5 . 通過不同線程讀寫變數(引自[3] Listing 5.2 Reading and writing variables from different threads)

注意到,非原子性的讀(2)和寫(3)若在沒有強制順序的情況下訪問同一變數,

將導致未定義行為。原子變數 data_ready 通過內存模型關係中的 happens-before
和 synchronized-with, 為它們 供了必要的順序:

    1. 寫 data(3)在寫 data_ready(4)前發生(happens-before);
    2. 寫 data_ready(4)在讀出 data_ready 的值為真(1)前發生(happens-before);
    3. data_ready 的值為真(1)在讀 data(2)前發生(happens-before).

通過 happens-before 的傳遞性及引入原子變數,我們保證了讀寫間的順序。
默認的原子操作使得寫變數在讀變數之前發生,然而原子操作也有用其他可選的操作方式,我們在此先介紹 synchronized-with 和 happens-before 關係。

3.1.1 Synchronized-with 關係

該關係 描述的是,對於suitably tagged(如默認的memory_order_seq_cst)在變數 x 上的寫操作 W(x) synchronized-with 在該變數上的讀操作 R(x), 這個讀操作欲讀取的值是 W(x) 或同一線程隨後的在 x 上的寫操作 W』,
或任意線程一系列的在 x 上的 read-modify-write 操作(如 fetch_add()或
compare_exchange_weak())而這一系列操作最初讀到 x 的值是 W(x) 寫入的值。

簡單地說,如果線程 A 寫了變數 x, 線程 B 讀了變數 x, 那麼我們就說線程 A,
B 間存在 synchronized-with 關係。

3.1.2 Happens-before 關係

Happens-before 指明了哪些指令將看到哪些指令的結果。

對於單線程而言,這很明了:如果一個操作 A 排列在另一個操作 B 之前,那
么這個操作 A happens-before B. 但如果多個操作發生在一條聲明中(statement),
那麼通常他們是沒有 happens-before 關係的,因為他們是未排序的。當然這也有
例外,比如逗號表達式。

對於多線程而言,如果一個線程中的操作 A inter-thread happens-before 另一個線程中的操作 B, 那麼 A happens-before B.

Inter-thread happens-before 關係

Inter-thread happens-before 概念相對簡單,並依賴於 synchronized-with 關係:
如果一個線程中的操作 A synchronized-with 另一個線程中的操作 B, 那麼 A inter-thread happens-before B. Inter-thread happens-before 關係具有傳遞性。

Inter-thread happens-before 可以與 sequenced-before 關係結合:如果 A
sequenced-before B, B inter-thread happens-before C, 那麼 A inter-thread happens-before C. 這揭示了如果你在一個線程中對數據進行了一系列操作,那麼你只需要一個 synchronized-with 關係,即可使得數據對於另一個執行操作 C 的線程可
見。

3.2 C++11 memory order

C++11 述了 6 種可以應用於原子變數的內存次序:

    • momory_order_relaxed,
    • memory_order_consume,
    • memory_order_acquire,
    • memory_order_release,
    • memory_order_acq_rel,
    • memory_order_seq_cst.

雖然共有 6 個選項,但它們表示的是三種內存模型:

    • sequential consistent(memory_order_seq_cst),
    • relaxed(memory_order_seq_cst).
    • acquire release(memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel),

這些不同的內存序模型在不同的CPU架構下會有不同的代價。這允許專家通過採用更合理的內存序獲得更大的性能 升;同時允許在對性能要求不是那麼嚴格的程序中採用默認的內存序,使得程序更容易理解。

不同的內存序模型與 synchronized-with 關係結合時,將產生不同的結果。

3.2.1 順序一致次序(sequential consisten ordering)

對應memory_order_seq_cst.

SC作為默認的內存序,是因為它意味著將程序看做是一個簡單的序列。如果對於一個原子變數的操作都是順序一致的,那麼多線程程序的行為就像是這些操作都以一種特定順序被單線程程序執行。

從同步的角度來看,一個順序一致的 store 操作 synchroniezd-with 一個順序一致的需要讀取相同的變數的 load 操作。除此以外,順序模型還保證了在 load 之後執行的順序一致原子操作都得表現得在 store 之後完成。

非順序一致內存次序(non-sequentially consistency memory ordering)強調對同一事件(代碼),不同線程可以以不同順序去執行,不僅是因為編譯器可以進行指令重排,也因為不同的 CPU cache 及內部緩存的狀態可以影響這些指令的執行。但所有線程仍需要對某個變數的連續修改達成順序一致。

3.2.2 鬆弛次序(relaxed ordering)

對應memory_order_relaxed.

在原子變數上採用 relaxed ordering 的操作不參與 synchronized-with 關係。在同一線程內對同一變數的操作仍保持happens-before關係,但這與別的線程無關。
在 relaxed ordering 中唯一的要求是在同一線程中,對同一原子變數的訪問不可以被重排。

沒有附加同步的情況下,「對每個變數的修改次序」(我的理解是對每個變數的寫串列化)是唯一一件被多個線程共享的事。

3.2.3 獲取-釋放次序(acquire-release ordering)

對應memory_order_release, memory_order_acquire, memory_order_acq_rel.

Acquire-release 中沒有全序關係,但它 供了一些同步方法。在這種序列模
型下,原子 load 操作是 acquire 操作(memory_order_acquire), 原子 store 操作是
release操作(memory_order_release), 原子read_modify_write操作(如fetch_add(),
exchange())可以是 acquire, release 或兩者皆是(memory_order_acq_rel). 同步是
成對出現的,它出現在一個進行 release 操作和一個進行 acquire 操作的線程間。
一個 release 操作 syncrhonized-with 一個想要讀取剛才被寫的值的 acquire 操作。

這意味著不同線程仍然看到了不一樣的次序,但這次序是被限制過了的。當我們在此再引入 happens-before 關係時,就可以實現更大規模的順序關係。([3] Listing 5.8, 5.9)

3.2.4 acquire-release間的數據依賴與memory_order_consume

memory_order_consume 是 acquire-release 順序模型中的一種,但它比較特殊,它為 inter-thread happens-before 引入了數據依賴關係:dependency-ordered-before 和 carries-a-dependency-to.

如同 sequenced-before, carries-a-dependency-to 嚴格應用於單個進程,建立了
操作間的數據依賴模型:如果操作 A 的結果被操作 B 作為操作數,那麼 A carries-a-dependency-to B. 這種關係具有傳遞性。

dependency-ordered-before 可以應用於線程與線程之間。這種關係通過一個
原子的被標記為 memory_order_consume 的 load 操作引入。這是
memory_order_acquire 內存序的特例:它將同步數據限定為具有直接依賴的數據。
一個被標記為 memory_order_release, memory_order_acq_rel 或
memory_order_seq_cst 的原子 store 操作 A dependency-ordered-before 一個被標記為 memory_order_consume 的欲讀取剛被改寫的值的原子 load 操作 B. 同時,如果 A dependency-order-before B, 那麼 A inter-thread happens-before B.

如果一個 store 操作被標記為 memory_order_release, memory_order_acq_rel
或 memory_order_seq_cst, 一個 load 操作被標記為 memory_order_cunsume,
memory_order_acquire 或 memory_order_seq_sct, 一連串 load 中的每個操作,讀取的都是之前操作寫下的值([3] Listing 5.11 舉例說明了這一點),那麼這一連串操作將構成 release sequence, 最初的
store 操作 synchronized-with 或 dependency-ordered-before 最後的 load.

不過[3]沒有給出任何使用memory_order_consume的代碼示例。

參考文獻

[1] Hennessy, John L., and David A. Patterson. Computer architecture: a quantitative approach. Elsevier, 2011.

[2] Adve, Sarita V., and Kourosh Gharachorloo. "Shared memory consistency models: A tutorial." Computer 29.12 (1996): 66-76.

[3] Williams, Anthony. C++ concurrency in action. Manning; Pearson Education, 2012.


讀一遍 C++11 §29.3 [atomics.order],總共 2 頁 A4 紙的內容;

或者參考 http://en.cppreference.com/w/cpp/atomic/memory_order,基本上可以認為是前者美化排版、改說一些人話後的版本。


理解了CPU架構和編譯器優化就自然理解了

CPU架構——在不同的CPU core上因cache不一致性導致的讀取/寫入不同步

編譯器優化——編譯器可能會把指令重新排序,導致即使上了同步原語,仍導致讀取/寫入亂序

一個是不同步,也就是期待的是同步,是一個執行期的隨機事件

一個是亂序,也就是打亂順序後的「固定順序」是一個確定事件


樓主應該是想要個通俗的解釋,詳細內容明白機制後看文檔就好。從軟體工程師的角度來看,內存模型是由於硬體為了優化性能而導致軟體必須配合的情況。

程序經由編譯器處理後變成了一條條的機器指令,每條指令又對應了更基礎的幾個硬體階段,各個指令在這些硬體階段里排隊處理,組成流水線。由於不同指令對應的硬體階段不同,導致有些時候流水線填不滿,影響性能。因此硬體會主動的對指令順序做微調,提升流水線的效率,也就是亂序執行。以上內容當然不夠嚴格,不過軟體工程師知道這些基本足夠了。

亂序執行是有規則的,在單線程下幾乎不需要軟體介入,但多線程就不一樣了。比如說我們的程序這麼寫:

thread1:
1: some_task = 123;
2: complete = true;

thread2:
while (!complete) sleep(1);
printf("got %d
", task_result);

程序本意是等任務完成後輸出 got 123,但結果很可能是 got 0,原因就是 1 和 2 兩條指令被調整了順序,亂序不當。

為解決這樣的問題,cpu 增加了用於控制亂序執行的指令,稱為內存柵欄(memory barrier)。在 1、2 之間插入柵欄就會強制 cpu 不做亂序,從而保證程序的正確性。柵欄分幾種,基本可分為:讀與讀不亂序、寫與寫不亂序和讀寫均不亂序。具體該在什麼位置插入哪種柵欄是比較高階的要求,大多數情況下善用鎖就好了,上例可以用一個互斥鎖保護,通常不會對程序最終性能產生影響。

最後說到 c++11 的內存模型。由於各種硬體的柵欄特性不同,出於可移植性的考慮,c++11 採用了學術上認可的最大模型,可以兼容各種硬體,但用起來比較麻煩。相比如果你只需要支持 x86,那麼只掌握 mfence 一種就足夠了。


如果一定要理解,首先要保證理解默認的和relaxed. 為了能夠理解這些概念,你還需要知道happens-before 和synchronized-with. 歸結到底,c++ concurrency in action 是必看。

我還是比較贊同@陳碩 的答案。簡單實用好。

另外,Facebook folly的代碼也可以看,也許對理解有幫助,比方說producer consumer queue。前提是你起碼有個概念想法了。


建議讀C++ Concurrency in Action第五章。作者是線程庫的提議者和實現者。但是這個深度未必能達到你的期望,因為這些非序列一致的內存序都是為專家準備的,一般不會深入講解。


C++11新特性這本書里有比較通俗易懂的講解。

c++ concurrency in action裡面提到了很多術語,比如sequence before, happen before等等,如果你真想摸透這些,請慢慢讀這本書,也可以到cppreference上看相關術語的嚴格定義。

不過你知道了也沒啥用,因為在x86架構上,只有序列一致,想要驗證什麼的,除非你能使用其他處理器架構的計算機。

比較反對陳碩這種回答,答非所問。就好比你問一句:找女友要看人品嗎?回答一句:「找什麼女友,搞基不就好了嘛」一樣


老老實實用mutex就好了嘛。

一個使用普通mutex的多線程程序,如果寫錯了,一般程序員,比如我,很容易分析出錯誤在哪——無非是漏了加鎖,或者加鎖次序錯亂——並加以改正。

如果用原子操作,除了最簡單的 atomic counter,如果寫錯了,你能通過讀代碼找出錯誤嗎?反正我是不能。換言之,你如何證明這一段代碼是正確的?reasoning 難多了。


看語言不如直接看操作系統。看這個

https://www.kernel.org/doc/Documentation/memory-barriers.txt


可以看看herb sutter的視頻:

atomic Weapons: The C++ Memory Model and Modern Hardware


其實裡面的模型已經是十多年前研究都比較成熟的內容了。後來的ARM也好各種多核架構也好,無不是選擇一種模型加以實現。你可以參考一下材料,雖然不是C++的,但道理都是通的。

http://www.cs.uiuc.edu/class/sp08/cs533/reading_list/adve95shared.pdf

還有一篇博士論文,我現在找不到了,是《計算機體系結構量化研究方法》的作者的學生寫的。


multithreading

不如先看看這個。


可以參考一下這個blog:Preshing on Programming,了解一下為什麼直接使用mutex沒有類似的問題,但並不盡然,比如單例模式下的double check的實現其實也是要考慮memory order的。


推薦閱讀:

怎樣設計一套程序設計語言?
近十年來編譯器有哪些關鍵的技術進步?
微軟的 C# 難學嗎?和 Python 比起來
Scala 編譯器是如何實現Nothing、Null 這種bottom type的?
printf("%s", NULL) 和 printf("%s
", NULL) 的區別?

TAG:編程語言 | C | C11 |