Go并發(fā)4種方法簡(jiǎn)明講解
一、goroutine
1、協(xié)程(Coroutine)
Golang 在語(yǔ)言層面對(duì)并發(fā)編程進(jìn)行了支持,使用了一種協(xié)程(goroutine)機(jī)制,
協(xié)程本質(zhì)上是一種用戶態(tài)線程,不需要操作系統(tǒng)來(lái)進(jìn)行搶占式調(diào)度,但是又寄生于線程中,因此系統(tǒng)開銷極小,可以有效的提高線程的任務(wù)并發(fā)性,而避免多線程的缺點(diǎn)。但是協(xié)程需要語(yǔ)言上的支持,需要用戶自己實(shí)現(xiàn)調(diào)度器,因?yàn)樵贕o語(yǔ)言中,實(shí)現(xiàn)了調(diào)度器所以我們可以很方便的能過(guò) go
關(guān)鍵字來(lái)使用協(xié)程。
func main() { for i := 0; i <10; i++ { go func(i int) { for { fmt.Printf("Hello goroutine %d\n",i) } }(i) } time.Sleep(time.Millisecond) }
最簡(jiǎn)單的一個(gè)并發(fā)編程小例子,并發(fā)輸出一段話。
我們同時(shí)開了10個(gè)協(xié)程進(jìn)行輸出,每次在fmt.printf
時(shí)交出控制權(quán)(不一定每次都會(huì)交出控制權(quán)),回到調(diào)度器中,再由調(diào)度器分配。
2、goroutine 可能切換的點(diǎn)
- I/O,Select
- channel
- 等待鎖
- 函數(shù)調(diào)用
- runtime.Gosched()
我們看一個(gè)小例子:
func main() { var a [10]int for i := 0; i <10; i++ { go func(i int) { for { a[i]++ } }(i) } time.Sleep(time.Millisecond) fmt.Println(a) }
在這里,代碼直接鎖死,程序沒(méi)有退出,因?yàn)樵趫?zhí)行函數(shù)中沒(méi)有協(xié)程的切換,因?yàn)?nbsp;main
函數(shù)也是一個(gè)協(xié)程。
如果想要程序退出,可以通過(guò) runtime.Gosched()
函數(shù),在執(zhí)行函數(shù)中添加一行。
for { a[i]++ runtime.Gosched() }
加上這個(gè)函數(shù)之后,代碼是可以正常執(zhí)行了,但是真的是正常執(zhí)行嗎?不一定,我們可以使用 -reac
命令來(lái)看一下數(shù)據(jù)是否有沖突:
這說(shuō)明數(shù)據(jù)還是有沖突的,數(shù)組a
中的元素一邊在做自增,一邊在輸出。解決這個(gè)問(wèn)題,我們只能使用 channel 來(lái)解決。
二、Channel
Channel 中 Go語(yǔ)言在語(yǔ)言級(jí)別提供了對(duì) goroutine 之間通信的支持,我們可以使用 channel 在兩個(gè)或者多個(gè)goroutine之間進(jìn)行信息傳遞,能過(guò) channel 傳遞對(duì)像的過(guò)程和調(diào)用函數(shù)時(shí)的參數(shù)傳遞行為一樣,可以傳遞普通參數(shù)和指針。
Channel 有兩種模式:
var ch1 = make(chan int) // 無(wú)緩沖 channel,同步 var ch2 = make(chan int, 2) // 有緩沖 channel, 異步
無(wú)緩沖的方式,數(shù)據(jù)進(jìn)入 channel 只要沒(méi)有被接收,就會(huì)處在阻塞狀態(tài)。
var ch1 = make(chan int) // 無(wú)緩沖 channel,同步 ch1 <- 1 ch1 <- 2 // error: all goroutines are asleep - deadlock! fmt.Println(<-ch1)
如果想要運(yùn)行,必須要再開一個(gè)協(xié)程不停的去請(qǐng)求數(shù)據(jù):
var ch1 = make(chan int) // 無(wú)緩沖 channel,同步 go func() { for { n := <-ch1 fmt.Println(n) } }() ch1 <- 1 ch1 <- 2
有緩沖的方式,只要緩沖區(qū)沒(méi)有滿就可以一直進(jìn)數(shù)據(jù),緩沖區(qū)在填滿之后沒(méi)有接收也會(huì)處理阻塞狀態(tài)。
func bufferChannel() { var ch2 = make(chan int,2) ch2<-1 ch2<-2 fmt.Println(ch2) // 不加這一行的話,是可以正常運(yùn)行的 ch2<-3 // error: all goroutines are asleep - deadlock! }
1、chaanel 指定方向
比如我現(xiàn)在有一個(gè)函數(shù)創(chuàng)建一個(gè) channel,并且不斷的需要消費(fèi)channel中的數(shù)據(jù):
func worker(ch chan int) { for { fmt.Printf("hello goroutine worker %d\n", <-ch) } } func createWorker() chan int{ ch := make(chan int) go worker(ch) return ch } func main() { ch := createWorker() ch<-1 ch<-2 ch<-3 time.Sleep(time.Millisecond) }
這個(gè)函數(shù)我是要給別人用的,但是我怎么保證使用 createWorker 函數(shù)創(chuàng)建的 channel 都是往里面?zhèn)魅霐?shù)據(jù)的呢?
如果外面有人消費(fèi)了這個(gè) channel 中的數(shù)據(jù),我們?cè)趺聪拗疲?/p>
這個(gè)時(shí)候,我們就可以給返回的channel 加上方向,指明這個(gè) channel 中能往里傳入數(shù)據(jù),不能從中取數(shù)據(jù):
func worker(ch <-chan int) { for { fmt.Printf("hello goroutine worker %d\n", <-ch) } } func createWorker() chan<- int{ ch := make(chan int) go worker(ch) return ch }
我們可以在返回 channel 的地方加上方向,指明返回的函數(shù)只能是一個(gè)往里傳入數(shù)據(jù),不能從中取數(shù)據(jù)。
并且我們還可以給專門消費(fèi)的函數(shù)加上一個(gè)方向,指明這個(gè)函數(shù)只能出不能進(jìn)。
2、channel 關(guān)閉
在使用 channel 的時(shí)候,隨說(shuō)我們可以等待channel中的函數(shù)使用完之后自己結(jié)束,或者等待 main 函數(shù)結(jié)束時(shí)關(guān)閉所有的 goroutine 函數(shù),但是這樣的方式顯示不夠優(yōu)雅。
當(dāng)一個(gè)數(shù)據(jù)我們明確知道他的結(jié)束時(shí)候,我們可以發(fā)送一個(gè)關(guān)閉信息給這個(gè) channel ,當(dāng)這個(gè) channel 接收到這個(gè)信號(hào)之后,自己關(guān)閉。
// 方法一 func worker(ch <-chan int) { for { if c ,ok := <- ch;ok{ fmt.Printf("hello goroutine worker %d\n", c) }else { break } } } // 方法二 func worker(ch <-chan int) { for c := range ch{ fmt.Printf("hello goroutine worker %d\n", c) } } func main() { ch := createWorker() ch<-1 ch<-2 ch<-3 close(ch) time.Sleep(time.Millisecond) }
通過(guò) Close
b函數(shù),我們可以能過(guò) channel 已經(jīng)關(guān)閉,并且我們還可以通過(guò)兩種方法判斷通道內(nèi)是否還有值。
三、Select
當(dāng)我們?cè)趯?shí)際開發(fā)中,我們一般同時(shí)處理兩個(gè)或者多個(gè) channel 的數(shù)據(jù),我們想要完成一個(gè)那個(gè) channel 先來(lái)數(shù)據(jù),我們先來(lái)處理個(gè)那 channel 怎么辦呢?
此時(shí),我們就可以使用 select 調(diào)度:
func genInt() chan int { ch := make(chan int) go func() { i := 0 for { // 隨機(jī)兩秒以內(nèi)生成一次數(shù)據(jù) time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) ch <- i i++ } }() return ch } func main() { var c1 = genInt() var c2 = genInt() for { select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <- c2: fmt.Printf("server 2 generator %d\n", n) } } }
1、定時(shí)器
for { tick := time.Tick(time.Second) select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <-c2: fmt.Printf("server 2 generator %d\n", n) case <-tick: fmt.Println("定時(shí)每秒輸出一次!") } }
2、超時(shí)
for { tick := time.Tick(time.Second) select { case n := <-c1: fmt.Printf("server 1 generator %d\n", n) case n := <-c2: fmt.Printf("server 2 generator %d\n", n) case <-tick: fmt.Println("定時(shí)每秒輸出一次!") case <-time.After(1300 * time.Millisecond): // 如果 1.3秒內(nèi)沒(méi)有數(shù)據(jù)進(jìn)來(lái),那么就輸出超時(shí) fmt.Println("timeout") } }
四、傳統(tǒng)的并發(fā)控制
1、sync.Mutex
type atomicInt struct { value int lock sync.Mutex } func (a *atomicInt) increment() { a.lock.Lock() defer a.lock.Unlock() // 使用 defer 解鎖,以防忘記 a.value++ } func main() { var a atomicInt a.increment() go func() { a.increment() }() time.Sleep(time.Millisecond) fmt.Println(a.value) }
2、sync.WaitGroup
type waitGrouInt struct { value int wg sync.WaitGroup } func (w *waitGrouInt) addInt() { w.wg.Add(1) w.value++ } func main() { var w waitGrouInt for i := 0; i < 10; i++ { w.addInt() w.wg.Done() } w.wg.Wait() fmt.Println(w.value) }
更多關(guān)于Go并發(fā)簡(jiǎn)明講解請(qǐng)查看下面的相關(guān)鏈接
- 使用google-perftools優(yōu)化nginx在高并發(fā)時(shí)的性能的教程(完整版)
- Golang極簡(jiǎn)入門教程(三):并發(fā)支持
- Go語(yǔ)言并發(fā)技術(shù)詳解
- Go語(yǔ)言并發(fā)模型的2種編程方案
- GO語(yǔ)言并發(fā)編程之互斥鎖、讀寫鎖詳解
- Go語(yǔ)言如何并發(fā)超時(shí)處理詳解
- 如何利用Golang寫出高并發(fā)代碼詳解
- golang實(shí)現(xiàn)并發(fā)數(shù)控制的方法
- 詳解Golang 中的并發(fā)限制與超時(shí)控制
- golang中sync.Map并發(fā)創(chuàng)建、讀取問(wèn)題實(shí)戰(zhàn)記錄
- Go 并發(fā)實(shí)現(xiàn)協(xié)程同步的多種解決方法
- 在Go中構(gòu)建并發(fā)TCP服務(wù)器
- Go 并發(fā)控制context實(shí)現(xiàn)原理剖析(小結(jié))
- Go并發(fā)調(diào)用的超時(shí)處理的方法
- golang 并發(fā)安全Map以及分段鎖的實(shí)現(xiàn)方法
- golang并發(fā)下載多個(gè)文件的方法
- Golang 實(shí)現(xiàn)分片讀取http超大文件流和并發(fā)控制
- golang gin 框架 異步同步 goroutine 并發(fā)操作
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)開發(fā)一個(gè)簡(jiǎn)單的gRPC Demo
這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)開發(fā)一個(gè)簡(jiǎn)單的gRPC Demo,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-07-07使用Golang開發(fā)一個(gè)簡(jiǎn)易版shell
這篇文章主要為大家詳細(xì)介紹了如何使用Golang開發(fā)一個(gè)簡(jiǎn)易版shell,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02golang中一種不常見的switch語(yǔ)句寫法示例詳解
這篇文章主要介紹了golang中一種不常見的switch語(yǔ)句寫法,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05