日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex]

發(fā)布時(shí)間:2023/11/27 生活经验 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex] 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文參考 《Go 語言實(shí)戰(zhàn)》

1. 競爭狀態(tài)簡述

如果兩個(gè)或者多個(gè) goroutine 在沒有互相同步的情況下,訪問某個(gè)共享的資源,并試圖同時(shí)讀和寫這個(gè)資源,就處于相互競爭的狀態(tài),這種情況被稱作競爭狀態(tài)(race candition)。

對一個(gè)共享資源的讀和寫操作必須是原子化的,換句話說,同一時(shí)刻只能有一個(gè) goroutine 對共享資源進(jìn)行讀和寫操作。

當(dāng)某些東西被認(rèn)為是原子的,或者具有原子性的時(shí)候,這意味著在它運(yùn)行的環(huán)境中,它是不可分割的或不可中斷的。

// 這個(gè)示例程序展示如何在程序里造成競爭狀態(tài)
// 實(shí)際上不希望出現(xiàn)這種情況
package mainimport ("fmt""runtime""sync"
)var (// counter是所有g(shù)oroutine都要增加其值的變量counter int// wg用來等待程序結(jié)束wg sync.WaitGroup
)// main是所有Go程序的入口
func main() {// 計(jì)數(shù)加2,表示要等待兩個(gè)goroutinewg.Add(2)// 創(chuàng)建兩個(gè)goroutinego incCounter(1)go incCounter(2)// 等待goroutine結(jié)束wg.Wait()fmt.Println("Final Counter:", counter)
}// incCounter增加包里counter變量的值
func incCounter(id int) {// 在函數(shù)退出時(shí)調(diào)用Done來通知main函數(shù)工作已經(jīng)完成defer wg.Done()for count := 0; count < 2; count++ {// 捕獲counter的值value := counter// 當(dāng)前goroutine從線程退出,并放回到隊(duì)列/*用于將 goroutine 從當(dāng)前線程退出,給其他 goroutine 運(yùn)行的機(jī)會(huì)。在兩次操作中間這樣做的目的是強(qiáng)制調(diào)度器切換兩個(gè) goroutine,以便讓競爭狀態(tài)的效果變得更明顯。*/runtime.Gosched()// 增加本地value變量的值value++// 將該值保存回countercounter = value}
}

輸出:

Final Counter: 2

變量 counter 會(huì)進(jìn)行 4 次讀和寫操作,每個(gè) goroutine 執(zhí)行兩次。但是,程序終止時(shí), counter 變量的值為2。

每個(gè) goroutine 都會(huì)覆蓋另一個(gè) goroutine 的工作。這種覆蓋發(fā)生在 goroutine 切換的時(shí)候。每個(gè) goroutine 創(chuàng)造了一個(gè) counter 變量的副本,之后就切換到另一個(gè) goroutine 。

當(dāng)這個(gè) goroutine 再次運(yùn)行的時(shí)候, counter 變量的值已經(jīng)改變了,但是 goroutine 并沒有更新自己的那個(gè)副本的值,而是繼續(xù)使用這個(gè)副本的值,用這個(gè)值遞增,并存回 counter 變量,結(jié)果覆蓋了另一個(gè) goroutine 完成的工作。

圖: 競爭狀態(tài)下程序行為的圖像表達(dá)

2. 鎖住共享資源

Go 語言提供了傳統(tǒng)的同步 goroutine 的機(jī)制,就是對共享資源加鎖。如果需要順序訪問一個(gè)整型變量或者一段代碼, atomicsync 包里的函數(shù)提供了很好的解決方案。

下面我們了解一下 atomic 包里的幾個(gè)函數(shù)以及 sync 包里的 mutex 類型。

2.1 原子函數(shù)

原子函數(shù)能夠以很底層的加鎖機(jī)制來同步訪問整型變量和指針.

示例 1:

package mainimport ("fmt""sync/atomic"
)var (// 序列號(hào)seq int64
)// 序列號(hào)生成器
func GenID() int64 {// 嘗試原子的增加序列號(hào)// 這里故意沒有使用 atomic .Addlnt64()的返回值作 為 GenID () 函數(shù)的返// 回值,因此會(huì)造成一個(gè)競態(tài)問題atomic.AddInt64(&seq, 1)return seq
}func main() {// 10個(gè)并發(fā)序列號(hào)生成for i := 0; i < 10; i++ {go GenID()}fmt.Println(GenID())
}

在運(yùn)行程序時(shí),為運(yùn)行參數(shù)加入 -race 參數(shù),開啟運(yùn)行時(shí)( runtime )對競態(tài)問題的分析,命令如下:

wohu@wohu-dev:~/gocode/src$ go run -race temp.go 
==================
WARNING: DATA RACE
Write at 0x0000005f8178 by goroutine 8:sync/atomic.AddInt64()/usr/local/go/src/runtime/race_amd64.s:276 +0xbmain.GenID()/home/wohu/gocode/src/temp.go:16 +0x43Previous read at 0x0000005f8178 by goroutine 7:main.GenID()/home/wohu/gocode/src/temp.go:17 +0x53Goroutine 8 (running) created at:main.main()/home/wohu/gocode/src/temp.go:23 +0x4fGoroutine 7 (finished) created at:main.main()/home/wohu/gocode/src/temp.go:23 +0x4f
==================
4
Found 1 data race(s)
exit status 66

修改該函數(shù)為下面即可正常。

// 序列號(hào)生成器
func GenID() int64 {// 嘗試原子的增加序列號(hào)return atomic.AddInt64(&seq, 1)
}

示例代碼 2:

// 這個(gè)示例程序展示如何使用atomic包來提供
// 對數(shù)值類型的安全訪問
package mainimport ("fmt""runtime""sync""sync/atomic"
)var (// counter是所有g(shù)oroutine都要增加其值的變量counter int32// wg用來等待程序結(jié)束wg sync.WaitGroup
)// main是所有Go程序的入口
func main() {// 計(jì)數(shù)加2,表示要等待兩個(gè)goroutinewg.Add(2)// 創(chuàng)建兩個(gè)goroutinego incCounter(1)go incCounter(2)// 等待goroutine結(jié)束wg.Wait()fmt.Println("Final Counter:", counter)
}// incCounter增加包里counter變量的值
func incCounter(id int) {// 在函數(shù)退出時(shí)調(diào)用Done來通知main函數(shù)工作已經(jīng)完成defer wg.Done()for count := 0; count < 2; count++ {// 安全地對counter加1atomic.AddInt32(&counter, 1)// 當(dāng)前goroutine從線程退出,并放回到隊(duì)列/*用于將 goroutine 從當(dāng)前線程退出,給其他 goroutine 運(yùn)行的機(jī)會(huì)。在兩次操作中間這樣做的目的是強(qiáng)制調(diào)度器切換兩個(gè) goroutine,以便讓競爭狀態(tài)的效果變得更明顯。*/runtime.Gosched()}
}

輸出:

Final Counter: 4

程序的第43行使用了 atmoic 包的 AddInt64 函數(shù)。這個(gè)函數(shù)會(huì)同步整型值的加法,方法是強(qiáng)制同一時(shí)刻只能有一個(gè) goroutine 運(yùn)行并完成這個(gè)加法操作。

當(dāng) goroutine 試圖去調(diào)用任何原子函數(shù)時(shí),這些 goroutine 都會(huì)自動(dòng)根據(jù)所引用的變量做同步處理。

另外兩個(gè)有用的原子函數(shù)是 LoadInt64StoreInt64 。這兩個(gè)函數(shù)提供了一種安全地讀和寫一個(gè)整型值的方式。

如下代碼示例程序使用 LoadInt64StoreInt64 來創(chuàng)建一個(gè)同步標(biāo)志,這個(gè)標(biāo)志可以向程序里多個(gè) goroutine 通知某個(gè)特殊狀態(tài)。

// 這個(gè)示例程序展示如何使用atomic包里的
// Store和Load類函數(shù)來提供對數(shù)值類型
// 的安全訪問
package mainimport ("fmt""sync""sync/atomic""time"
)var (// shutdown是通知正在執(zhí)行的goroutine停止工作的標(biāo)志shutdown int64// wg用來等待程序結(jié)束wg sync.WaitGroup
)// main是所有Go程序的入口
func main() {// 計(jì)數(shù)加2,表示要等待兩個(gè)goroutinewg.Add(2)// 創(chuàng)建兩個(gè)goroutinego doWork("A")go doWork("B")// 給定goroutine執(zhí)行的時(shí)間time.Sleep(1 * time.Second)// 該停止工作了,安全地設(shè)置shutdown標(biāo)志fmt.Println("Shutdown Now")atomic.StoreInt64(&shutdown, 1)// 等待goroutine結(jié)束wg.Wait()
}// doWork用來模擬執(zhí)行工作的goroutine,
// 檢測之前的shutdown標(biāo)志來決定是否提前終止
func doWork(name string) {// 在函數(shù)退出時(shí)調(diào)用Done來通知main函數(shù)工作已經(jīng)完成defer wg.Done()for {fmt.Printf("Doing %s Work\n", name)time.Sleep(250 * time.Millisecond)// 要停止工作了嗎?if atomic.LoadInt64(&shutdown) == 1 {fmt.Printf("Shutting %s Down\n", name)break}}
}

2.2 互斥鎖

另一種同步訪問共享資源的方式是使用互斥鎖( mutex )?;コ怄i這個(gè)名字來自互斥(mutual exclusion)的概念。

互斥鎖用于在代碼上創(chuàng)建一個(gè)臨界區(qū),保證同一時(shí)間只有一個(gè) goroutine 可以執(zhí)行這個(gè)臨界區(qū)代碼。

// 這個(gè)示例程序展示如何使用互斥鎖來
// 定義一段需要同步訪問的代碼臨界區(qū)
// 資源的同步訪問
package mainimport ("fmt""runtime""sync"
)var (// counter是所有g(shù)oroutine都要增加其值的變量counter int// wg用來等待程序結(jié)束wg sync.WaitGroup// mutex 用來定義一段代碼臨界區(qū)mutex sync.Mutex
)// main是所有Go程序的入口
func main() {// 計(jì)數(shù)加2,表示要等待兩個(gè)goroutinewg.Add(2)// 創(chuàng)建兩個(gè)goroutinego incCounter(1)go incCounter(2)// 等待goroutine結(jié)束wg.Wait()fmt.Printf("Final Counter: %d\n", counter)
}// incCounter使用互斥鎖來同步并保證安全訪問,
// 增加包里counter變量的值
func incCounter(id int) {// 在函數(shù)退出時(shí)調(diào)用Done來通知main函數(shù)工作已經(jīng)完成defer wg.Done()for count := 0; count < 2; count++ {// 同一時(shí)刻只允許一個(gè)goroutine進(jìn)入// 這個(gè)臨界區(qū)mutex.Lock(){	 // 使用大括號(hào)只是為了讓臨界區(qū)看起來更清晰,并不是必需的。// 捕獲counter的值value := counter// 當(dāng)前goroutine從線程退出,并放回到隊(duì)列runtime.Gosched()// 增加本地value變量的值value++// 將該值保存回countercounter = value}mutex.Unlock()// 釋放鎖,允許其他正在等待的goroutine// 進(jìn)入臨界區(qū)}
}

counter 變量的操作在第 46 行和第 60 行的 Lock()Unlock() 函數(shù)調(diào)用定義的臨界區(qū)里被保護(hù)起來。

同一時(shí)刻只有一個(gè) goroutine 可以進(jìn)入臨界區(qū)。之后,直到調(diào)用 Unlock() 函數(shù)之后,其他 goroutine 才能進(jìn)入臨界區(qū)。當(dāng)?shù)?52 行強(qiáng)制將當(dāng)前 goroutine 退出當(dāng)前線程后,調(diào)度器會(huì)再次分配這個(gè) goroutine 繼續(xù)運(yùn)行。當(dāng)程序結(jié)束時(shí),我們得到正確的值 4,競爭狀態(tài)不再存在。

2.3 讀寫互斥鎖 sync.RWMutex

在讀多寫少的環(huán)境中,可以優(yōu)先使用讀寫互斥鎖, sync 包中的 RWMutex 提供了讀寫互斥鎖的封裝。

package mainimport ("fmt""sync""time"
)var (count int// 變量對應(yīng)的讀寫互斥鎖countGuard sync.RWMutex
)func GetCount() int {countGuard.RLock()defer countGuard.RUnlock()return count
}func SetCount(c int) {countGuard.Lock(){count += c}countGuard.Unlock()
}func main() {// 可以進(jìn)行并發(fā)安全的設(shè)置for i := 0; i < 10; i++ {go SetCount(2)}time.Sleep(2 * time.Second)// 可以進(jìn)行并發(fā)安全的讀取fmt.Println(GetCount())
}

總結(jié)

以上是生活随笔為你收集整理的Go 学习笔记(23)— 并发(02)[竞争,锁资源,原子函数sync/atomic、互斥锁sync.Mutex]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。