2017-05-28 43 views
2

我有下面(源 - https://mechanical-sympathy.blogspot.in/2011/09/single-writer-principle.html)的發言幾個澄清的:澄清單個寫入器

「86/64有一個存儲器模型,由此加載/存儲存儲器操作都保留命令,從而存儲如果你嚴格遵守單一作者原則,就不需要屏障。

在x86/x64上,根據內存模型,可以使用較舊的存儲重新排序「加載」,因此多個線程在多個內核之間改變相同的數據時需要內存屏障。 「

這是否意味着:
1.在一個單一的核心,加載/存儲內存操作總是順序?
因此,單個核心繫統上的單個寫入器線程(和多個讀取器線程)不需要「同步」來解決可見性問題?
2.對於多核,可以重新安排負載,並從其他核心發起存儲?
因此,單個編寫器線程(以及運行在其他核心上的多個讀取器線程)不需要「同步」來解決可見性問題(因爲不會有任何存儲)?因此,如果我們嚴格維護一個作者 - 我們實際上可以避免在原始鎖中讀取和寫入時使用'synchronized'的做法。 我們實際上可以完全消除'synchronized'嗎?

+1

您使用的是Java。您應該尊重Java內存模型。不是x86/64內存模型。 –

回答

1

在單個內核中,內存訪問是按順序還是亂序進行並不重要。如果有一個核心,它將始終感知一致的值,因爲讀取請求將由保存未寫入數據的相同緩存提供。

但是,這對於Java程序來說是無關緊要的,因爲作爲Java語言規範一部分的Java內存模型並沒有爲多線程程序做出這樣的保證。事實上,術語「記憶障礙」根本沒有出現在規範中。

你必須認識到的是,你編寫的Java代碼不會是CPU將執行的x86/x64代碼。優化的本地代碼不會像你的源代碼。代碼優化的一個基本部分是消除冗餘讀寫,甚至是條件代碼部分,假設值之間不會發生虛假變化,這對單線程執行來說總是正確的。

如果由於沒有正確的線程安全構造的多線程操作導致基礎假設失效,此優化代碼將產生不一致的結果。在規範中,這是一個可接受的不一致性,因爲無論如何執行一致結果的內存模型都會導致性能顯着下降。像同步或易失性寫入和讀取一樣,線程安全結構不僅告訴JVM插入內存屏障的位置(如果底層體系結構需要它),還會告訴JVM何處以及如何限制代碼優化。

這就是爲什麼a)在操作可變共享狀態時需要正確的線程安全構造的原因,以及b)即使在CPU /硬件級別沒有需要的內存屏障,這些構造可能會有性能損失。

+0

雖然你在這裏,但就我所知,這是關於'lazySet'和'StoreLoad'的。 *是*,我們使用Java編碼,但如果您處理Martin Thomson所做的數據,請考慮此問題 - 這對您有所幫助。他必須說的是,一個'StoreLoad'將強制緩衝區消耗 - 因此很昂貴,它是唯一必須在x86上實現並由運行時發佈的。它本身不是'fence',而是具有相同效果的'lock addl'。由於x86是一種強大的內存模型,因此不需要其他防護。 – Eugene

+0

只有使用單個寫入器原則才能釋放緩衝區。因此這很便宜。我可能永遠不會處理這個問題,就像* ever *;但我喜歡這些細節。這當然是我的理解 – Eugene

+1

@Eugene:這個問題顯然是用[java]和[java-memory-model]標記的,因此,無論鏈接文章是關於什麼的,我都回答* OP的問題* *不是*「關於'lazySet'和'StoreLoad'「,但是關於由於目標硬件體系結構而寫破碎的代碼的誘惑。 – Holger

0

大免責聲明

一些我在這裏寫的事情實際上測試 - 就像重新排序,沖洗等;他們中的一些人花了很多時間閱讀,我希望我說得對。

一切被重新排序,而不是重新排序的策略,讓你的程序運行它在數年前下降了道路。只要輸出不改變,操作就會按照他們的要求重新排序。

例如:

static int sum(int x, int y){ 
    x = x + 1; 
    y = y + 1; 
    return x + y; 
} 

你真的不關心爲了在實現這些工作,只要結果是正確的,你做了什麼?

沒有內存屏障(通常稱爲StoreLoad|StoreStore|LoadStore|LoadLoad),任何操作都可能發生變化。爲了保證有些操作不需要move beyond a fence,有cpu fences執行。 Java有幾種生成方法 - volatile,synchroniztion,Unsafe/VarHandle(可能有其他的,我不知道)。

基本上當你寫一個volatile例如,出現這種情況:

volatile x... 

[StoreStore] - inserted by the compiler 
[LoadStore] 
x = 1; // volatile store 
[StoreLoad] 

... 

[StoreLoad] 
int t = x; // volatile load 
[LoadLoad] 
[LoadStore] 

讓我們這個例子的一個子集:

​​

這意味着任何Store或的Load變量不能用x = 1重新排序。同樣的原則適用於其他障礙。

馬丁湯姆森說,那是什麼on x864分之3的屏障是免費的,已被髮出的只有一個:StoreLoad。它們是免費的,因爲x86具有強大的內存模型,這意味着其他操作默認情況下不會重新排序。在其他的CPU上,其中一些操作也相當便宜(如果我在ARM上有錯誤lwsync - 輕量級同步;名稱應該是自解釋的)。

此外,CPU和緩存之間還有一點緩衝區 - 稱爲Store Buffer。當您將某些內容寫入變量時,它不會直接進入緩存(s)。它進入該緩衝區。當它已滿(或被強制通過StoreLoad排空)時,它會將寫入寫入緩存 - 並且最多可以使用cache coherency protocol來同步所有緩存中的數據。

馬丁說,如果你有多個作家,你必須多次發出StoreLoad - 因此它是昂貴的。如果你有一個作家,你不需要。緩衝區滿時會排空。什麼時候發生?那麼有時,理論上可能永遠不會,實際上相當快。

一些很棒的資源(這些資源有時讓我整夜都沒有睡覺,所以小心!):

這些一StoreStore順便說一句你寫的每一個最終的變量在構造函數中時間:

private final int i ; 

public MyObj(int i){ 
    this.i = i; 
     // StoreStore here 
} 

LazySet

Shipilev Volatile

And my all time favorite!