欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析golang如何在多線程中避免CPU指令重排

 更新時間:2024年03月27日 16:42:59   作者:Cao?Lilu  
這篇文章主要為大家詳細介紹了golang在多線程中避免CPU指令重排的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

起因

golang 的發(fā)明初衷便是多線程,是一門專門用于多線程高并發(fā)的編程語言。其獨創(chuàng)的 GMP 模型在多線程的開發(fā)上提供了很大的便利。

現(xiàn)代計算機基本上都是多核 CPU 的結(jié)構(gòu)。CPU 在進行指令運行的時候,為了提高效率,會在一些情況下對指令進行重排序,其目的是在保持運行結(jié)果和不重拍序的指令一致的前提下,提高程序的運行效率。但是對于多線程并行執(zhí)行來說,我們可能需要對此額外關(guān)注,以避免重排對多線程的影響。

英特爾在其 x86/64 體系結(jié)構(gòu)規(guī)范第 3 卷 §8.2.3 中列出了幾個這樣的問題。這里有一個最簡單的例子。假設(shè)內(nèi)存中有兩個整數(shù) X 和 Y,最初的值都是 0。兩個并行運行的處理器執(zhí)行以下的機器代碼:

雖然在這個例子中使用匯編語言,但這確實是說明 CPU 排序的比較好的方式。每個處理器將 1 存儲到其中一個整數(shù)變量中,然后將另一個整數(shù)加載到寄存器中。(r1 和 r2 只是實際 x86 寄存器(如 eax)的占位符名稱。)

現(xiàn)在,無論哪個處理器先將 1 寫入內(nèi)存,都很自然地希望另一個處理器讀取回該值,這意味著我們最終應(yīng)該得到 r1=1、r2=1,或者兩者都有。但根據(jù)英特爾的規(guī)范,情況不一定如此。在規(guī)范中,在這個例子的結(jié)尾,r1 和 r2 都等于 0 是合法的!這可能是一個違反直覺的結(jié)果!

理解這一點的一種方法是,與大多數(shù)處理器系列一樣,英特爾x86/64處理器可以根據(jù)某些規(guī)則重新排序機器指令的內(nèi)存交互,只要它永遠不會改變單線程程序的執(zhí)行。特別地,允許每個處理器將存儲的效果延遲超過來自不同位置的任何加載。因此,最終可能會出現(xiàn)指令按以下順序執(zhí)行的情況:

程序測試

CPU 指令重排導致的問題

在下面的程序中,來實現(xiàn)上述 CPU 指令重排在多線程中造成的數(shù)據(jù)不一致現(xiàn)象。下面代碼中,聲明了 a,b,x,y 四個變量并將其默認值設(shè)置為 0。聲明兩個 go routine 分別執(zhí)行目標操作(見代碼)。正常情況,不管下面 a = 1,x = b,b = 1, y = a 這四條質(zhì)量如何執(zhí)行,如果沒有重排產(chǎn)生,那么永遠不可能出現(xiàn) x == 0 和 y == 0 同時發(fā)生的情況。

但是由于 CPU 指令重排的原因,在實際執(zhí)行的情況下,在第 1738, 110002, 12987 次測試到了 CPU 指令重排的發(fā)生。

func withCpuReordering() {
	index := 0
	for {
		index += 1

		var a, b int32 = 0, 0
		var x, y int32 = 0, 0

		var wg sync.WaitGroup
		wg.Add(2)

		go func() {
			defer wg.Done()

			a = 1
			x = b
		}()

		go func() {
			defer wg.Done()

			b = 1
			y = a
		}()
		wg.Wait()

		if x == 0 && y == 0 {
			panic("CPU Reordering occurs!")
		} else {
			fmt.Println("Now processing in loop", index)
		}
	}
}

綁定 CPU 消除指令重排

上述例子的現(xiàn)象只在多核 CPU 執(zhí)行的之后才會出現(xiàn),也就是線程并行執(zhí)行的時候才會出現(xiàn)。如果我們將上述程序的執(zhí)行都鎖定在一個 CPU 上,也就能避免這種情況的發(fā)生。

在下面代碼中,我們制定 go routine 最多只能使用一個 CPU。在整個測試過程中,沒有出現(xiàn) x == 0 和 y == 0 同時發(fā)生的情況。

func main() {
	runtime.GOMAXPROCS(1)
	withCpuReordering()
}

原因在于指令重排的目的在于提高執(zhí)行效率,而不是改變執(zhí)行結(jié)果。

通過內(nèi)存屏障消除指令重排

在 Go 語言的 sync/atomic 包中,原子操作函數(shù)的實現(xiàn)會使用 CPU 提供的原子操作指令,以實現(xiàn)對共享變量的原子讀寫操作。這些原子操作指令通常會在硬件層面實現(xiàn)內(nèi)存屏障(Memory Barrier),以確保對共享變量的讀寫操作在不同的 CPU 核心之間具有一定的有序性。

在下面的代碼中,通過 atomic 包中的原子操作函數(shù)代替了上述代碼中的賦值操作,從而解決了執(zhí)行結(jié)果不一致的情況。

func withoutCpuReordering() {
	index := 0
	for {
		index += 1

		var a, b int32 = 0, 0
		var x, y int32 = 0, 0

		var wg sync.WaitGroup
		wg.Add(2)

		go func() {
			defer wg.Done()

			atomic.StoreInt32(&a, 1)
			atomic.StoreInt32(&x, atomic.LoadInt32(&b))
		}()

		go func() {
			defer wg.Done()

			atomic.StoreInt32(&b, 1)
			atomic.StoreInt32(&y, atomic.LoadInt32(&a))
		}()
		wg.Wait()

		if x == 0 && y == 0 {
			panic("CPU Reordering occurs!")
		} else {
			fmt.Println("Now processing in loop", index)
		}
	}
}

類似的指令和不同的平臺

所有這些不同的 CPU 系列,每個都有獨特的指令來強制執(zhí)行內(nèi)存排序,編譯器根據(jù)不同的 CPU 系列將代碼編譯成不同的指令,并且每個跨平臺項目都實現(xiàn)了自己的可移植層。這些都無助于簡化無鎖編程!這也是最近引入 C++11 原子庫標準的部分原因。這是一種標準化的嘗試,使編寫可移植的無鎖代碼變得更容易。

比如 mfence 指令特定于 x86/64 的 CPU 架構(gòu)。如果想使代碼更具可移植性,可以將此內(nèi)在特性封裝在預(yù)處理器宏中。Linux 內(nèi)核將其封裝在一個名為 smp_mb 的宏,以及相關(guān)的宏中,如 smp_rmb 和 smp_wmb,并在不同的體系結(jié)構(gòu)上提供了替代實現(xiàn)。例如,在 PowerPC 上,smp_mb 被實現(xiàn)為 sync。

到此這篇關(guān)于淺析golang如何在多線程中避免CPU指令重排的文章就介紹到這了,更多相關(guān)go多線程避免CPU指令重排內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang實現(xiàn)Mongo數(shù)據(jù)庫增刪改查操作

    Golang實現(xiàn)Mongo數(shù)據(jù)庫增刪改查操作

    本文主要介紹了Golang實現(xiàn)Mongo數(shù)據(jù)庫增刪改查操作,我們使用了 MongoDB的官方Go驅(qū)動程序,實現(xiàn)了插入、查詢、更新和刪除操作,感興趣的可以了解一下
    2024-01-01
  • GoLang context包的使用方法介紹

    GoLang context包的使用方法介紹

    日常Go開發(fā)中,Context包是用的最多的一個了,幾乎所有函數(shù)的第一個參數(shù)都是ctx,那么我們?yōu)槭裁匆獋鬟fContext呢,Context又有哪些用法,底層實現(xiàn)是如何呢?相信你也一定會有探索的欲望,那么就跟著本篇文章,一起來學習吧
    2023-03-03
  • 淺析Go語言中的同步與異步處理

    淺析Go語言中的同步與異步處理

    在開發(fā)過程中,當需要同時處理多個操作時,開發(fā)者經(jīng)常面臨同步和異步兩種處理方式的選擇,下面小編就來和大家詳細介紹一下Go語言中的同步與異步處理吧
    2023-11-11
  • Go并發(fā)編程之sync.Once使用實例詳解

    Go并發(fā)編程之sync.Once使用實例詳解

    sync.Once使用起來很簡單, 下面是一個簡單的使用案例,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-11-11
  • Golang實現(xiàn)異步上傳文件支持進度條查詢的方法

    Golang實現(xiàn)異步上傳文件支持進度條查詢的方法

    這篇文章主要介紹了Golang實現(xiàn)異步上傳文件支持進度條查詢的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10
  • Golang切片和數(shù)組拷貝詳解(淺拷貝和深拷貝)

    Golang切片和數(shù)組拷貝詳解(淺拷貝和深拷貝)

    這篇文章主要為大家詳細介紹一下Golang切片拷貝和數(shù)組拷貝,文中有詳細的代碼示例供大家參考,需要的可以參考一下
    2023-04-04
  • Golang中如何實現(xiàn)枚舉詳析

    Golang中如何實現(xiàn)枚舉詳析

    舉就是將數(shù)據(jù)值一一列出來,枚舉可以用來表示一些固定的值,枚舉是常量組成的,下面這篇文章主要給大家介紹了關(guān)于Golang中如何實現(xiàn)枚舉的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • go 代碼的調(diào)試---打印調(diào)用堆棧的實例

    go 代碼的調(diào)試---打印調(diào)用堆棧的實例

    下面小編就為大家?guī)硪黄猤o 代碼的調(diào)試---打印調(diào)用堆棧的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • Go?Gin框架優(yōu)雅重啟和停止實現(xiàn)方法示例

    Go?Gin框架優(yōu)雅重啟和停止實現(xiàn)方法示例

    Web應(yīng)用程序中,有時需要重啟或停止服務(wù)器,無論是因為更新代碼還是進行例行維護,這時需要保證應(yīng)用程序的可用性和數(shù)據(jù)的一致性,就需要優(yōu)雅地關(guān)閉和重啟應(yīng)用程序,即不丟失正在處理的請求和不拒絕新的請求,本文將詳解如何在Go語言中使用Gin這個框架實現(xiàn)優(yōu)雅的重啟停止
    2024-01-01
  • 關(guān)于升級go1.18的goland問題詳解

    關(guān)于升級go1.18的goland問題詳解

    作為一個go語言程序員,覺得自己有義務(wù)為go新手開一條更簡單便捷的上手之路,下面這篇文章主要給大家介紹了關(guān)于升級go1.18的goland問題的相關(guān)資料,需要的朋友可以參考下
    2022-11-11

最新評論