標籤:

acquire and release semantics in mutex的理解?

我知道在mutex中,lock具有acquire語義且unlock具有release語義,這樣能保證兩者之間的代碼不能reorder,但是我想光是這樣並不能保證side effect被其他線程看到,對acquire和release的解釋如下:Acquire semantics prevent memory reordering of the read-acquire with any read or write operation which follows it in program order且Release semantics prevent memory reordering of the write-release with any read or write operation which precedes it in program order,這其中並沒有cache coherence相關的內容,除非release語義具有刷新store buffer的能力才行吧,但是上面的解釋好像只寫了memory oreder相關的內容,因此我想問問大家release具有invalidate cache的能力不


先回答題主最後的問題.:

release具有invalidate cache的能力不?

答案是有. 但其實這種說法並不十分準確. 正確的說法是: acquire和release一起作用, 保證了memory order和invalidate cache語義, 從而實現對臨界區訪問的序列化.

嘗試解釋一下. 有錯請指出.

1 Mutex是一種同步機制, 或者直接叫做一種鎖. 所以先討論下通用鎖的機制.

1.1 鎖是為了保護可能被從並發的多個程序執行流訪問同一塊數據對象而引入的序列化手段. 並發的多個程序執行流可能有以下幾種場景:

1)多個CPU情況. 它們並發訪問同一臨界區.

2) 只有一個CPU情況. 此時只有一個執行流, 它訪問數據對象C. 但是, 該執行流可以被中斷, 比如來了一個非同步網路數據包, 我們不妨稱之為中斷. 處理這個數據包的中斷常式, 如果也訪問這個數據

對象, 那麼這個對象也變成臨界區. 此時場景等價於多CPU場景, 並發訪問同一臨界區.

3) 有些設有DMA能力的設備, 能繞過CPU直接訪問內存. 所以, 這種場景下, 也能構成CPU, 設備

並發訪問同一臨界區的情況.

為了避免這種竟爭情況, 要引入一種序列化機制, 使得並發的訪問變成序列化. 這種序列化機制是一種憑證, 每參與者都必須先獲取該憑證, 才能執行操作, 操作完畢, 要歸還憑證. 形象化地稱該憑證為鎖. 所以,

第一, 鎖具有序列化的作用

1.2 有了序列化保證並未完事. 在我們以鎖的作比擬的例子中, 我們深埋一個假設, 一個拿鎖進臨界區的參與者, 它見到的"作案現場" 必定是上一個進入者離開時的現場. 這在現實中是必然的, 也是直觀的. 但在系統中, 這並不是必然的. 主要有以下兩種原因:

1) 由於出於性能的考量, 在超線程, 流水線等技術持續壓榨CPU的同時, CPU還會在合適的情況下採取亂序執行, 也就是, 真實的指令執行順序並不一定等同程序編寫時的程序順序.

2) 此外, CPU的訪問是存在層級的, 簡言之, 即先cache, 再內存. 在多CPU的體系架構中, 每個CPU都有自己局部的內存, 當然它也可以訪問別的CPU的局部內存. 這意味著, CPU訪問離它近的內存快, 訪問遠的內存慢. 所以, 這會由於cache的刷新延遲而導致訪問內存一致性的問題. 也就是內存被修改了, cache還沒來得及刷新, CPU就來訪問了, 此時訪問的是一個無效的舊值. 也就是, 執行順序並不一定等同於觀察順序

鎖的實現還必須保證解決這兩個問題, 所以,

第二, 鎖具有保持訪存一致性的作用

2. 回到Mutex, 它作為一種互斥鎖, 也必須有以上二個作用. 它是這樣保證這二點要求的:

2.1 互斥機制. 拿到該鎖者, 獨佔訪問. 稱之為ACQUIRE操作. 訪問完畢, 釋放鎖, 稱之為RELEASE

操作. 這種互斥保證了序列化.

2.2 於ACQUIRERELEASE 上附加保持訪存一致性的語義: 即題主所提到的:

1) ACQUIRE: 對於所有其它參與者來說, 在此操作的所有讀寫操作必然發生在ACQUIRE這個動作之後. 前面半句狀語從句很重要, 它保證執行順序等同於觀察順序 .

2) RELEASE: 對於所有其它參與者來說, 在此操作的所有讀寫操作必然發生在RELEASE這個動作之前. 前面半句狀語從句很重要, 它保證執行順序等同於觀察順序 .

注意, 這其中任意一個操作, 都只保證了一半的順序:

對於ACQUIRE來說, 並沒保證ACQUIRE前的讀寫操作不會發生在ACQUIRE動作之後.

對於RELEASE來說, 並沒保證RELEASE後的讀寫操作不會發生在RELEASE動作之前.

但是, ACQUIRE 和 RELEASE 配對起來使用後, 就有了完全順序. 成為一個屏障性的保證, 術語叫memory barrier. 如下:

上圖, CPU A, CPU B先後進入由Mutex M保護的臨界區.

看標1. 2 這兩行. 這兩行, 一個RELEASE, 再一個ACQUIRE. 實現了一個屏障. 它保證 :

12 之 前的讀寫, 不會穿越 這個屏障,

12 之 後的讀寫, 不會穿越 這個屏障.

只要看下前面的 ACQUIRE RELEASE 的語義, 就可證明.

同時, ACQUIRE RELEASE 的語義中 "對於所有其它參與者來說"這一要求, 又保證了執行順序等同於觀察順序.

因此, Mutex能實現序列化的同時, ACQUIRE RELEASE 的語義保證, 共同實現了cache invalidation.

不同平台,內在順序模型(memory ordering model)不同。有些強模型的平台,如x86, 採用的是很強的內存序模型,叫process consistency memory ordering. 也就是,它保證執行順序等同於觀察順序。在處理器級別已經有cache coherence保證。對於弱模型平台,則必須在ACQUIRE RELEASE 的中顯式實現。


強內存模型可以說是自帶,但是也要防止某些指令reorder.

弱內存模型上則需要插入機器指令級別的barrier來維護內存一致性.


mutex 既保證互斥性,也需要保證可見性。一般裡面都會帶柵欄的。


memory order描述的是指令之間的可見性順序,比如「看到x的最新值時一定也能看到y的最新值」。所以memory order並不保證可見性,可見性都是by best efforts of CPU。


計算機科學最重要的思想之一:分層。

你說的這個問題基本上是層沒分清。

我不太清楚你這個release/acquire從哪裡看到的,但感覺應該是C11或者C++11標準中的內存模型相關章節吧。

C/C++11標準是給編譯器寫手看的,從語義層面描述了符合標準的程序的正確行為是什麼樣的,對於內存模型,基本上描述了內存修改以什麼順序執行,以及何時變得可見。

而你說的store buffer/invalidate cache什麼東西是一個計算機體系結構的實現細節。這種屬於編譯器實現時候,根據平台特性來寫的具體實現。這些是不會寫在語言的標準里的。

如果你說的是X86平台的話,我記得好像不用invalidate cache,編譯器不要自己reorder就好了,X86平台的緩存一致性保證好像是相當強的。


acquire和release的實現者必須保證這樣的語義,無論是如何指令重拍優化,在內存還是在寄存器。


題主在描述中也提到了acquire和release是「語義」,那麼會不會invalidate cache就是具體的平台實現了。

我不懂硬體,如果是x86的話,應該是 「會」 (如有錯誤。。請務必指出。。 ,,???,, )


specification和implementation是兩個概念

coherency和consistency也是兩個概念

cache實現技術主要和coherency相關

acquire/release主要和consistency相關,是architecture specification的範疇,visibility是memory model的要求也是spec

有無所謂cache/store buf結構都應該滿足spec要求的visibility,而有無local cache,local cache是否有主動被動的invalidation和具體微架構實現有關不能一概而論


推薦閱讀:

貼吧用戶幻之上帝真實的水平怎樣?跟輪子哥比如何?
計算機大牛們,看C++有關書籍是不是一遍就看懂了,總感覺自己笨,有些地方需要看幾遍才懂?
如何評價call_in_stack這個庫?
Unreal4有哪些令你印象深刻拍案叫絕的設計?

TAG:編程 | C | 多線程 |