2016-09-30 42 views
3

我想讓「原子vs非原子」的概念在我腦海中解決。我的第一個問題是我找不到「現實生活中的比喻」。像客戶/餐廳關於原子操作或類似的東西。原子操作會阻止其他線程嗎?

另外我想了解一下原子操作是如何將自己置於線程安全編程的。

在這篇博文中, http://preshing.com/20130618/atomic-vs-non-atomic-operations/ 它被提及爲:作用於共享存儲器

的操作是原子的,如果它在 單一步驟相對於其他線程完成。 當原子存儲位於一個共享變量進行 ,沒有其他的線程能夠觀察 修改完成一半。當在一個 共享變量上執行原子加載時,它會讀取整個值,因爲它在單個時間點出現。非原子加載和存儲不保證這些 。

「沒有其他線程可以觀察到修改半完成」是什麼意思?

這意味着線程將等待原子操作完成?那個線程怎麼知道這個操作是原子的?例如在.NET中,我可以理解你是否鎖定了設置標誌的對象以阻止其他線程。但是原子呢?其他線程如何知道原子操作和非原子操作之間的區別?

另外,如果上述語句爲真,那麼所有的原子操作都是線程安全的嗎?

+0

請不要公佈答案添加到您的問題。這將打破問答格式,並使其不易讀。換句話說,答案不屬於問題。謝謝。 – 2501

回答

3

讓我們來澄清一下什麼是原子,什麼是塊。原子性意味着操作要麼完全執行,並且所有的副作用都是可見的,或者根本不執行。因此,所有其他線程可以在操作之前或之後看到狀態。由互斥體保護的代碼塊也是原子,我們只是不稱它爲操作。原子操作是特殊的CPU指令,它在概念上與通常的操作類似於互斥量守護的操作(你知道互斥量是多少,所以我會用它,儘管它是用原子操作實現的)。 CPU有一套有限的操作,它可以自動執行,但由於硬件支持,它們非常快。

當我們討論線程塊時,我們通常會在對話中涉及互斥鎖,因爲守護它們的代碼可能需要相當長的時間來執行。所以我們說這個線程等待一個互斥量。對於原子操作情況是一樣的,但它們很快,我們通常不在意這裏的延遲,所以不太可能聽到「阻塞」和「原子操作」這兩個詞彙。

這意味着線程將等到原子操作完成?

是的,它會等待。 CPU將限制訪問變量所在的內存塊,並且其他CPU內核將等待。請注意,出於性能原因,塊僅在原子操作本身之間進行。 CPU核心被允許緩存變量以供讀取。

該線程如何知道該操作是原子?

使用特殊的CPU指令。它只是寫在你的程序中,應該以原子方式執行特定的操作。

其他信息:

沒有與原子操作更棘手的部分。例如,在現代CPU上,通常所有讀取和寫入的原始類型都是原子的。但是CPU和編譯器可以對它們重新排序。所以有可能你改變了一些結構,設置一個標誌,告訴它它被改變了,但是在結構被實際提交到內存之前,CPU會重新編寫和設置標誌。當您使用原子操作時,通常會做一些額外的工作來防止不必要的重新排序。如果你想知道更多,你應該閱讀關於記憶障礙。

簡單的原子存儲和寫入沒有那麼有用。爲了最大限度地利用原子操作,你需要更復雜的東西。最常見的是CAS - 比較和交換。您將變量與一個值進行比較,只有在比較成功時才更改它。

1

您所描述的原子操作是在處理器內部的指令和硬件將確保直到原子寫入完成讀取不能在內存位置發生。這保證了一個線程之前寫或寫操作後的值讀取值,但沒有在兩者之間 - 有沒有從寫,另一半從之前讀取值的字節數的一半機會寫完之後。

針對處理器運行的代碼甚至沒有意識到這個塊,但它與使用lock語句確保更復雜的操作(由許多低級指令組成)是原子的確沒有區別。

單個原子操作始終是線程安全的 - 硬件保證了操作的效果是原子的 - 它永遠不會在中途獲得中斷。

一組原子操作的是不是在絕大多數情況下的原子(我不是專家,所以我不想做一個明確的說法,但我想不出的情況下,其中這將是不同的) - 這就是爲什麼複雜操作需要鎖定的原因:整個操作可能由多條原子指令組成,但整個操作可能會在這兩條指令中的任何一條之間被中斷,從而造成另一個線程看起來不成熟的可能性結果。鎖定可確保在共享數據上運行的代碼在其他操作完成(可能通過多個線程切換器)之前無法訪問該數據。

一些例子顯示在this question/answer,但你會發現更多的通過搜索。

1

作爲「原子」是適用於由所述執行(無論是硬件或編譯器,一般來說)執行的操作的屬性。對於現實生活中的類比,請查看需要交易的系統,例如銀行賬戶。從一個賬戶到另一個賬戶的轉賬涉及從一個賬戶和存款轉到另一個賬戶,但通常這些應該以原子方式執行 - 沒有時間錢已被提取但尚未存入,反之亦然。

所以,繼續爲您的問題比喻:

是什麼意思「沒有其它線程可以觀察修改完成一半」?

這意味着任何線程都無法在一個帳戶中提取但未存入另一個帳戶的狀態下觀察這兩個帳戶。

在機而言,這意味着在一個線程的值的原子讀出不會看到值與從由另一個線程原子寫入之前的一些比特,並從相同的寫操作後一些比特。比單個讀或寫更復雜的各種操作也可以是原子的:例如,「比較和交換」是通常實現的原子操作,該操作檢查變量的值,將其與另一值相比較,並用另一值替換如果比較值相等,則爲原子值 - 例如,如果比較成功,則另一個線程不可能在操作的比較部分和交換部分之間寫入不同的值。任何由另一個線程寫入的內容都將在原子比較和交換之前完全執行或完全執行。

標題你的問題是:

將原子操作阻塞其他線程?

在「塊」的通常含義中,答案是否定的;一個線程中的原子操作本身不會導致執行停止在另一個線程中,儘管它可能導致活鎖情況或以其他方式阻止進度。

這意味着線程將等到原子操作完成?

從概念上講,這意味着他們將永遠不會需要等待。操作要麼完成,要麼不完成;它從未中途完成。在實踐中,原子操作可以使用互斥鎖來實現,並且性能成本很高。許多(如果不是大多數)現代處理器在硬件級別支持各種原子基元。

另外,如果上述語句爲真,那麼所有的原子操作都是線程安全的嗎?

如果您撰寫的原子操作,他們不再是原子的。也就是說,我可以執行一個原子比較和交換操作,然後再執行另一個操作,兩個比較和交換將分別爲原子,但它們是可分的。因此你仍然可能有併發錯誤。

+0

謝謝。我需要一些許可。關於你最後的陳述,如果它不阻止,在交易的一半方面,第二個線程將知道如何返回並完成交易。除非我們指定,否則我假設在原子事務下沒有spinwait的概念。如果第一個線程以原子方式存取,第二個線程正在嘗試讀取餘額,如果原子操作爲中途,第二個線程將返回以前的值還是等待操作完成?如果第二種情況是真的,它是如何知道/通知回來並讀取更新的值? –

+0

@Teomanshipahi你需要明白原子操作真的很小:CAS,增量,存儲,加載。所以線程上下文在執行時切換爲NEWER。當我們談論他們的線程安全性時,我們實際上談論了正在不同CPU核心上執行的線程。 –

+0

並沒有原子操作「阻止我,直到別人通知我」。您只是以原子方式修改內存,這就是與其他可用線程的所有交互。 –

2

典型的現代CPU,原子操作是由原子是這樣的:

當指令發出訪問內存,核心的邏輯嘗試把核心的緩存在正確的狀態來訪問內存。通常情況下,這種狀態將在內存訪問發生之前實現,所以不會有任何延遲。

雖然另一個內核正在對內存塊執行原子操作,但它會將該內存鎖定在其自己的緩存中。這可以防止任何其他內核獲取訪問該內存的權限,直到原子操作完成。

除非兩個核心碰巧正在執行對許多相同內存區域的訪問,並且這些訪問中的很多是寫入,否則這通常不會涉及任何延遲。這是因爲原子操作非常快,通常核心事先知道需要訪問哪些內存。

因此,比如說一段內存最後在內核1上訪問,現在內核2想要做一個原子增量。當內核的預取邏輯在指令流中看到對該內存的修改時,它將指示緩存獲取該內存。緩存將使用核心總線從核心1的緩存中獲取內存區域的所有權,並且會將該區域鎖定在其自己的緩存中。

此時,如果另一個內核嘗試讀取或修改該區域的內存,它將無法在其緩存中獲取該區域,直到釋放該鎖定爲止。這種通信發生在連接緩存的總線上,並且恰好發生在哪裏取決於內存所處的緩存。(如果根本不在緩存中,則必須轉到主內存。)

緩存鎖定通常不會被描述爲阻塞線程,因爲它速度如此之快,並且因爲內核通常能夠在嘗試獲取鎖定在其他緩存中的內存區域時執行其他操作。從更高級代碼的角度來看,原子實現通常被認爲是實現細節。

所有原子操作都提供了保證不會看到中間結果。這就是他們的原子。

0

原子操作意味着系統完全執行操作或根本不執行操作。讀取或寫入int64是原子的(64位系統& 64位CLR),因爲系統在一次操作中讀取/寫入8個字節,讀者看不到新值的一半被存儲,而舊值的一半。但要小心:

long n = 0; // writing 'n' is atomic, 64bits OS & 64bits CLR 
long m = n; // reading 'n' is atomic 
....// some code 
long o = n++; // is not atomic : n = n + 1 is doing a read then a write in 2 separate operations 

爲了讓原子發生++可以使用互鎖API的N:

long o = Interlocked.Increment(ref n); // other threads are blocked while the atomic operation is running 
相關問題