Go中并發(fā)控制的實(shí)現(xiàn)方式總結(jié)
Go的并發(fā)控制
在Go實(shí)際開發(fā)中,并發(fā)安全是老生常談的事情,在并發(fā)下,goroutine
之間的存在數(shù)據(jù)資源等方面的競(jìng)爭(zhēng)。
為了保證數(shù)據(jù)一致性、防止死鎖等問(wèn)題的出現(xiàn),在并發(fā)中需要使用一些方式來(lái)實(shí)現(xiàn)并發(fā)控制。
并發(fā)控制的目的是確保在多個(gè)并發(fā)執(zhí)行的線程或進(jìn)程中,對(duì)共享資源的訪問(wèn)和操作能夠正確、有效地進(jìn)行,并且避免出現(xiàn)競(jìng)態(tài)條件和數(shù)據(jù)不一致的問(wèn)題。
在Go中,可以通過(guò)以下幾種方式來(lái)實(shí)現(xiàn)并發(fā)控制:
1、channel
channel
通道主要用于于goroutine
之間通信和同步的機(jī)制。通過(guò)使用channel
,可以在不同的goroutine
之間進(jìn)行數(shù)據(jù)的發(fā)送與接收,從而實(shí)現(xiàn)協(xié)調(diào)和控制并發(fā),以達(dá)到并發(fā)控制。
根據(jù)channel
的類型,可以實(shí)現(xiàn)不同的并發(fā)控制效果:
無(wú)緩沖channel
當(dāng)使用make
初始化時(shí),不指定channel
的容量大小,即初始化無(wú)緩沖channel
;
當(dāng)發(fā)送方向無(wú)緩沖channel
發(fā)送消息數(shù)據(jù)時(shí),如果發(fā)送后channel
的數(shù)據(jù)未被接收方獲取,則當(dāng)前goroutine
會(huì)阻塞在發(fā)送語(yǔ)句中,直到有接收者準(zhǔn)備好接收數(shù)據(jù)為止,即無(wú)緩沖通道要求發(fā)送操作和接收操作同時(shí)準(zhǔn)備好才能完成通信。這樣做是確保了發(fā)送和接收的同步,避免了數(shù)據(jù)競(jìng)爭(zhēng)和不確定性。
package main import ( "fmt" "time" ) func main() { // 創(chuàng)建一個(gè)無(wú)緩沖通道 ch := make(chan int) // 啟動(dòng)一個(gè) goroutine 接收數(shù)據(jù) go func() { time.Sleep(time.Second * 5) fmt.Println("等待接收數(shù)據(jù)") data := <-ch // 接收數(shù)據(jù) fmt.Println("接收到數(shù)據(jù):", data) }() fmt.Println("發(fā)送數(shù)據(jù)") // 發(fā)送數(shù)據(jù),由于匿名函數(shù)goroutine睡眠,無(wú)緩沖通道內(nèi)數(shù)據(jù)沒(méi)有g(shù)oroutine接收,因此會(huì)阻塞。5s后被接收則繼續(xù)執(zhí)行 ch <- 100 time.Sleep(time.Second) fmt.Println("程序結(jié)束") }
在上述代碼中,創(chuàng)建了一個(gè)無(wú)緩沖通道
ch
。然后在一個(gè)單獨(dú)的goroutine
中啟動(dòng)了一個(gè)接收操作,等待從通道ch
中接收數(shù)據(jù)。接下來(lái),在
main goroutine
中執(zhí)行發(fā)送操作,向通道ch
發(fā)送數(shù)據(jù)100
。由于無(wú)緩沖通道的特性,當(dāng)發(fā)送語(yǔ)句
ch <- 100
執(zhí)行時(shí),由于沒(méi)有接收者準(zhǔn)備好接收數(shù)據(jù)(單獨(dú)的goroutine
處于5s
睡眠),發(fā)送操作會(huì)被阻塞。接收方的
goroutine
在接收數(shù)據(jù)之前會(huì)一直等待。當(dāng)接收方的
goroutine
準(zhǔn)備好之后,發(fā)送操作完成,數(shù)據(jù)被成功發(fā)送并被接收方接收,然后程序繼續(xù)執(zhí)行后續(xù)語(yǔ)句,打印出相應(yīng)的輸出。
需要注意的是,在使用無(wú)緩沖channel
時(shí),如果沒(méi)有接收者,發(fā)送操作將會(huì)永久阻塞,可能會(huì)導(dǎo)致死鎖,因此在使用無(wú)緩沖通道時(shí),需要確保發(fā)送和接收操作能夠匹配。
有緩沖channel
當(dāng)使用make
初始化時(shí),可以指定channel
的容量大小,即初始化有緩沖channel
,通道的容量表示通道中最大能存放的元素?cái)?shù)量。
當(dāng)發(fā)送方發(fā)送數(shù)據(jù)到有緩存
channel
時(shí),如果緩沖區(qū)滿了,則發(fā)送方會(huì)被阻塞直到有緩沖空間可以接收這個(gè)消息數(shù)據(jù);當(dāng)接收方在有緩沖
channel
接收數(shù)據(jù)時(shí),如果緩沖區(qū)為空,則接收方會(huì)被阻塞直到channel
有數(shù)據(jù)可讀;
無(wú)論是緩存 channel
還是無(wú)緩沖 channel
,都是并發(fā)安全的,即多個(gè) goroutine
可以同時(shí)發(fā)送和接收數(shù)據(jù),而不需要額外的同步機(jī)制。
但是,由于緩存 channel
具有緩存空間,因此在使用時(shí)需要特別注意緩存空間的大小,避免過(guò)度消耗內(nèi)存或者發(fā)生死鎖等問(wèn)題。
2、sync.WaitGroup
在sync
包中,sync.WaitGroup
可以在并發(fā)goroutine
之間起到執(zhí)行屏障的效果。WaitGroup
提供了用于創(chuàng)建多個(gè)goroutine時(shí),能夠等待多個(gè)并發(fā)執(zhí)行的代碼塊在達(dá)到WaitGroup
顯示指定的同步條件后,才可以繼續(xù)執(zhí)行Wait
的后續(xù)代碼。在使用sync.WaitGroup
實(shí)現(xiàn)同步模式下,從而起到并發(fā)控制的效果。
在Go中,sync.WaitGroup
類型提供了如下幾個(gè)方法:
方法名 | 功能說(shuō)明 |
---|---|
func (wg * WaitGroup) Add(delta int) | 等待組計(jì)數(shù)器 + delta |
(wg *WaitGroup) Done() | 等待組計(jì)數(shù)器-1 |
(wg *WaitGroup) Wait() | 阻塞直到等待組計(jì)數(shù)器變?yōu)? |
示例:
package main import ( "fmt" "sync" ) // 聲明全局等待組變量 var wg sync.WaitGroup func printHello() { fmt.Println("Hello World") wg.Done() // 完成一個(gè)任務(wù)后,調(diào)用Done()方法,等待組減1,告知當(dāng)前goroutine已經(jīng)完成任務(wù) } func main() { wg.Add(1) // 等待組加1,表示登記一個(gè)goroutine go printHello() fmt.Println("main") wg.Wait() // 阻塞當(dāng)前goroutine,直到等待組中的所有g(shù)oroutine都完成任務(wù) } // 執(zhí)行結(jié)果 main Hello World
3、sync.Mutex
sync.Mutex
是 Go 語(yǔ)言中的一個(gè)互斥鎖(Mutex
)類型,用于實(shí)現(xiàn)對(duì)共享資源的互斥訪問(wèn)。
互斥鎖是一種常見的并發(fā)控制機(jī)制,它能夠確保在同一時(shí)刻只有一個(gè) goroutine
可以訪問(wèn)被保護(hù)的資源,從而避免數(shù)據(jù)競(jìng)爭(zhēng)和不確定的結(jié)果。
互斥鎖的作用可以有以下幾個(gè)方面:
- 保護(hù)共享資源:當(dāng)多個(gè)
goroutine
并發(fā)訪問(wèn)共享資源時(shí),通過(guò)使用互斥鎖可以限制只有一個(gè)goroutine
可以訪問(wèn)共享資源,從而避免競(jìng)態(tài)條件和數(shù)據(jù)不一致的問(wèn)題。 - 實(shí)現(xiàn)臨界區(qū):互斥鎖可以將一段代碼標(biāo)記為臨界區(qū),只有獲取了鎖的
goroutine
才能執(zhí)行該臨界區(qū)的代碼,其他goroutine
則需要等待解鎖,才能夠訪問(wèn)臨界區(qū)內(nèi)的代碼塊。
互斥鎖的基本使用方式是,通過(guò)調(diào)用 Lock()
方法獲取鎖,執(zhí)行臨界區(qū)代碼,然后調(diào)用 Unlock()
方法釋放鎖。在獲取鎖之后,其他 goroutine
將會(huì)被阻塞,直到當(dāng)前 goroutine
釋放鎖為止。Lock()
方法與Unlock()
底層的實(shí)現(xiàn)原理是使用原子操作來(lái)維護(hù)Mutex
的state
狀態(tài)。
sync.Mutex
中,除了最基本的互斥鎖外,還提供讀寫鎖,在讀多寫少的場(chǎng)景下,相比互斥鎖性能上能夠有所提升。
channel 與 Mutex 對(duì)比例子
在自增操作x++
中,該操作并非原子操作,因此在多個(gè)goroutine
對(duì)全局變量x
進(jìn)行自增時(shí),會(huì)出現(xiàn)數(shù)據(jù)覆蓋的情況,因此可以通過(guò)一些方法來(lái)實(shí)現(xiàn)并發(fā)控制,例如channel
、互斥鎖
、原子操作
。
可以對(duì)比一下channel
與互斥鎖
在實(shí)現(xiàn)并發(fā)控制時(shí)的執(zhí)行時(shí)間:
- 使用
channel
package main import ( "fmt" "sync" "time" ) var x int64 var wg sync.WaitGroup func main() { startTime := time.Now() ch := make(chan struct{}, 1) for i := 0; i < 10000; i++ { wg.Add(1) go func() { defer wg.Done() ch <- struct{}{} x++ <-ch }() } wg.Wait() endTime := time.Now() fmt.Println(x) // 10000 fmt.Println(endTime.Sub(startTime)) // 6.2933ms }
- 使用
Mutex
package main import ( "fmt" "sync" "time" ) var x int64 var wg sync.WaitGroup var lock sync.Mutex func main() { startTime := time.Now() for i := 0; i < 10000; i++ { wg.Add(1) go func() { defer wg.Done() lock.Lock() x++ lock.Unlock() }() } wg.Wait() endTime := time.Now() fmt.Println(x) // 10000 fmt.Println(endTime.Sub(startTime)) // 3.0835ms }
可以對(duì)比兩種方法的執(zhí)行時(shí)間,在啟動(dòng)10000
個(gè)goroutine
執(zhí)行10000
次全局變量x++
時(shí),channel
實(shí)現(xiàn)并發(fā)控制全局變量x++
的執(zhí)行時(shí)間為6.2933ms
(存在波動(dòng)),而使用Mutex
提供的互斥鎖實(shí)現(xiàn)并發(fā)控制全局變量x++
的執(zhí)行時(shí)間為3.0835ms
(存在波動(dòng)),大約在兩倍左右,這是為什么呢?
原因在于channel
的操作涉及到**goroutine
之間的調(diào)度和上下文的切換**,而互斥鎖底層使用了Go的原子操作,執(zhí)行時(shí)間較短,因?yàn)榛コ怄i的操作相對(duì)輕量,不涉及goroutine
的調(diào)度以及上下文的切換。
在開發(fā)過(guò)程中,選擇使用通道還是互斥鎖取決于具體的場(chǎng)景與需求,并不是一定說(shuō)使用鎖就好,需要根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景來(lái)進(jìn)行選擇。如果需要更細(xì)粒度的控制和更高的并發(fā)性能,可以優(yōu)先考慮使用互斥鎖。
4、atomic原子操作
Go語(yǔ)言提供了原子操作用于對(duì)內(nèi)存中的變量進(jìn)行同步訪問(wèn),避免了多個(gè)goroutine
同時(shí)訪問(wèn)同一個(gè)變量時(shí)可能產(chǎn)生的競(jìng)態(tài)條件。
sync/atomic
包提供了原子加操作、比較并交換等方法提供一系列原子操作,這些方法利用底層的原子指令,確保對(duì)內(nèi)存中的變量進(jìn)行原子級(jí)別的訪問(wèn)和修改,從而實(shí)現(xiàn)并發(fā)控制。
package main import ( "fmt" "sync" "sync/atomic" ) var x int64 var wg sync.WaitGroup // 使用原子操作 func atomicAdd() { atomic.AddInt64(&x, 1) wg.Done() } func main() { for i := 0; i < 10000; i++ { wg.Add(1) go atomicAdd() // 原子操作add函數(shù) } wg.Wait() fmt.Println(x) // 10000 }
一些常用的原子操作函數(shù):
Add
函數(shù):AddInt32
、AddInt64
、AddUint32
、AddUint64
等方法,用于對(duì)變量進(jìn)行原子加操作。CompareAndSwap
函數(shù):CompareAndSwapInt32
、CompareAndSwapInt64
、CompareAndSwapUint32
、CompareAndSwapUint64
等,用于比較并交換操作,當(dāng)舊值等于給定值時(shí),將新值賦值到指定地址中。Load
函數(shù):LoadInt32
、LoadInt64
、LoadUint32
、LoadUint64
等,用于加載操作,返回指定地址中存儲(chǔ)的值。Store
函數(shù):StoreInt32
、StoreInt64
、StoreUint32
、StoreUint64
等,用于存儲(chǔ)操作,將給定的值存儲(chǔ)到指定地址中。Swap
函數(shù):SwapInt32
、SwapInt64
、SwapUint32
、SwapUint64
等,用于交換操作,將指定地址中存儲(chǔ)的值和給定的值進(jìn)行交換,并返回原值。
以上就是Go中并發(fā)控制的實(shí)現(xiàn)方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Go并發(fā)控制實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Go語(yǔ)言動(dòng)態(tài)并發(fā)控制sync.WaitGroup的靈活運(yùn)用示例詳解
- golang?waitgroup輔助并發(fā)控制使用場(chǎng)景和方法解析
- GO中sync包自由控制并發(fā)示例詳解
- Go語(yǔ)言并發(fā)處理效率響應(yīng)能力及在現(xiàn)代軟件開發(fā)中的重要性
- Go中Goroutines輕量級(jí)并發(fā)的特性及效率探究
- 盤點(diǎn)總結(jié)2023年Go并發(fā)庫(kù)有哪些變化
- Go語(yǔ)言單線程運(yùn)行也會(huì)有的并發(fā)問(wèn)題解析
- Go語(yǔ)言并發(fā)編程之控制并發(fā)數(shù)量實(shí)現(xiàn)實(shí)例
相關(guān)文章
go語(yǔ)言如何使用gin庫(kù)實(shí)現(xiàn)SSE長(zhǎng)連接
所謂長(zhǎng)連接指在一個(gè)TCP連接上可以連續(xù)發(fā)送多個(gè)數(shù)據(jù)包,在TCP連接保持期間,如果沒(méi)有數(shù)據(jù)包發(fā)送,需要雙方發(fā)檢測(cè)包以維持此連接,一般需要自己做在線維持,下面這篇文章主要給大家介紹了關(guān)于go語(yǔ)言如何使用gin庫(kù)實(shí)現(xiàn)SSE長(zhǎng)連接的相關(guān)資料,需要的朋友可以參考下2023-06-06Go 1.22對(duì)net/http包的路由增強(qiáng)功能詳解
Go 1.22 版本對(duì) net/http 包的路由功能進(jìn)行了增強(qiáng),引入了方法匹配(method matching)和通配符(wildcards)兩項(xiàng)新功能,本文將給大家詳細(xì)的介紹一下Go 1.22對(duì)net/http包的路由增強(qiáng)功能,需要的朋友可以參考下2024-02-02使用Gin框架返回JSON、XML和HTML數(shù)據(jù)
Gin是一個(gè)高性能的Go語(yǔ)言Web框架,它不僅提供了簡(jiǎn)潔的API,還支持快速的路由和中間件處理,在Web開發(fā)中,返回JSON、XML和HTML數(shù)據(jù)是非常常見的需求,本文將介紹如何使用Gin框架來(lái)返回這三種類型的數(shù)據(jù),需要的朋友可以參考下2024-08-08Gin與Mysql實(shí)現(xiàn)簡(jiǎn)單Restful風(fēng)格API實(shí)戰(zhàn)示例詳解
這篇文章主要為大家介紹了Gin與Mysql實(shí)現(xiàn)簡(jiǎn)單Restful風(fēng)格API示例詳解,有需要的朋友可以借鑒參考下希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11golang替換無(wú)法顯示的特殊字符(\u0000,?\000,?^@)
這篇文章主要介紹了golang替換無(wú)法顯示的特殊字符,包括的字符有\(zhòng)u0000,?\000,?^@等,下文詳細(xì)資料,需要的小伙伴可以參考一下2022-04-04