簡體   English   中英

原子操作會阻止其他線程嗎?

[英]Will atomic operations block other threads?

我試圖讓“原子與非原子”概念在我的腦海中解決。 我的第一個問題是我找不到“現實生活中的類比”。 就像原子操作上的客戶/餐館關系或類似的東西。

另外,我想了解原子操作如何將自己置於線程安全編程中。

在這篇博文中; http://preshing.com/20130618/atomic-vs-non-atomic-operations/它被提及為:

如果相對於其他線程在一個步驟中完成,則對共享內存執行操作是原子操作。 當對共享變量執行原子存儲時,沒有其他線程可以觀察到修改半完成 當對共享變量執行原子加載時,它會讀取單個時刻出現的整個值。 非原子載荷和商店不做出這些保證。

“沒有其他線程可以觀察修改半完成”是什么意思?

這意味着線程將等待直到原子操作完成? 該線程如何知道該操作是原子的? 例如在.NET中我可以理解,如果你鎖定對象,你設置一個標志來阻止其他線程。 但原子怎么樣? 其他線程如何知道原子操作和非原子操作之間的區別?

如果上面的語句為真,那么所有原子操作都是線程安全的嗎?

讓我們澄清什么是原子和什么是塊。 原子性意味着操作要么完全執行,所有的副作用都是可見的,要么根本不執行。 因此,所有其他線程可以在操作之前或之后查看狀態。 由互斥鎖保護的代碼塊也是原子的,我們只是不稱它為操作。 原子操作是特殊的CPU指令,在概念上類似於互斥鎖保護的常規操作(你知道互斥鎖是什么,所以我會使用它,盡管它是使用原子操作實現的)。 CPU具有一組有限的操作,可以自動執行,但由於硬件支持,它們非常快。

當我們討論線程塊時,我們通常會在對話中涉及互斥鎖,因為由它們保護的代碼可能需要相當長的時間才能執行。 所以我們說線程在互斥上等待。 對於原子操作情況是相同的,但是它們很快並且我們通常不關心這里的延遲,因此不太可能聽到單詞“阻塞”和“原子操作”在一起。

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

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

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

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

附加信息:

原子操作有更棘手的部分。 例如,在現代CPU上,通常所有原始類型的讀寫都是原子的。 但是CPU和編譯器可以重新排序。 因此,您可能更改某個結構,設置一個標志,告知它已更改,但CPU重新排序在結構實際提交到內存之前寫入並設置標志。 當您使用原子操作時,通常會做一些額外的工作來防止意外的重新排序。 如果你想了解更多,你應該閱讀內存障礙。

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

在典型的現代CPU中,原子操作以這種方式原子化:

當發出訪問內存的指令時,內核的邏輯會嘗試將內核的緩存置於正確的狀態以訪問該內存。 通常,這種狀態將在內存訪問必須發生之前實現,因此沒有延遲。

當另一個核心在一塊內存上執行原子操作時,它會將該內存鎖定在自己的緩存中。 這可以防止任何其他核心在原子操作完成之前獲取訪問該內存的權限。

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

因此,假設最后一次在核心1上訪問了一塊內存,現在核心2想要進行原子增量。 當核心的預取邏輯在指令流中看到對該存儲器的修改時,它將指示高速緩存獲取該存儲器。 緩存將使用核心間總線從核心1的緩存中獲取該內存區域的所有權,並將該區域鎖定在其自己的緩存中。

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

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

所有原子操作都可以保證不會看到中間結果。 這就是使它們成為原子的原因。

您描述的原子操作是處理器內的指令,硬件將確保在原子寫入完成之前不能在內存位置進行讀取。 這可以保證線程寫入之前讀取值或在寫入操作之后讀取值,但不介於兩者之間 - 在寫入之前沒有機會讀取值的一半字節,而在寫入之后讀取另一半的字節。

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

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

在絕大多數情況下,一組原子操作不是原子操作(我不是專家所以我不想做出明確的陳述,但我想不出這種情況會有所不同) - 這是為什么復雜操作需要鎖定:整個操作可能由多個原子指令組成,但整個操作可能仍然在這兩個指令中的任何一個之間被中斷,從而產生另一個線程看到半成品結果的可能性。 鎖定可確保在共享數據上運行的代碼在其他操作完成之前無法訪問該數據(可能通過多個線程切換)。

此問題/答案中顯示了一些示例,但您可以通過搜索找到更多示例。

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

所以,繼續你的問題的類比:

“沒有其他線程可以觀察修改半完成”是什么意思?

這意味着沒有線程可以在一個帳戶中提取但是沒有存入另一個帳戶的狀態下觀察這兩個帳戶。

在機器術語中,它意味着一個線程中的值的原子讀取將看不到具有來自另一個線程的原子寫入之前的一些位的值,以及來自相同寫入操作之后的一些位。 比單個讀取或寫入更復雜的各種操作也可以是原子的:例如,“比較和交換”是一種常見的原子操作,它檢查變量的值,將其與第二個值進行比較,並將其替換為另一個值。值,如果比較值相等,則原子 - 例如,如果比較成功,則另一個線程不可能在比較和操作的交換部分之間寫入不同的值。 任何另一個線程的寫入將完全在原子比較和交換之前或之后完成。

你問題的標題是:

原子操作會阻止其他線程嗎?

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

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

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

如果上面的語句為真,那么所有原子操作都是線程安全的嗎?

如果你組成原子操作,它們就不再是原子的。 也就是說,我可以做一個原子比較和交換操作,然后是另一個,兩個比較和交換將分別是原子的,但它們是可分的。 因此,您仍然可能存在並發錯誤。

原子操作意味着系統完全執行操作或根本不執行操作。 讀取或寫入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

要使n ++發生原子性,您可以使用Interlocked API:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM