go獲取協(xié)程(goroutine)號的實例
我就廢話不多說了,大家還是直接看代碼吧~
func GetGID() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] b = bytes.TrimPrefix(b, []byte("goroutine ")) b = b[:bytes.IndexByte(b, ' ')] n, _ := strconv.ParseUint(string(b), 10, 64) return n }
補(bǔ)充:Go語言并發(fā)協(xié)程Goroutine和通道channel
Go語言并發(fā)協(xié)程Goroutine
1.1 Go語言競爭狀態(tài)
有并發(fā),就有資源競爭,如果兩個或者多個 goroutine 在沒有相互同步的情況下,訪問某個共享的資源,比如同時對該資源進(jìn)行讀寫時,就會處于相互競爭的狀態(tài),這就是并發(fā)中的資源競爭。
并發(fā)本身并不復(fù)雜,但是因為有了資源競爭的問題,就使得我們開發(fā)出好的并發(fā)程序變得復(fù)雜起來,因為會引起很多莫名其妙的問題。
以下代碼就會出現(xiàn)競爭狀態(tài):
import ( "fmt" "runtime" "sync" ) var ( count int32 wg sync.WaitGroup ) func main() { wg.Add(2) go incCount() go incCount() wg.Wait() fmt.Println(count) } func incCount() { defer wg.Done() for i := 0; i < 2; i++ { value := count runtime.Gosched() value++ count = value } }
count 變量沒有任何同步保護(hù),所以兩個 goroutine 都會對其進(jìn)行讀寫,會導(dǎo)致對已經(jīng)計算好的結(jié)果被覆蓋,以至于產(chǎn)生錯誤結(jié)果。
代碼中的 runtime.Gosched() 是讓當(dāng)前 goroutine 暫停的意思,退回執(zhí)行隊列runq,讓其他等待的 goroutine 運行,目的是為了使資源競爭的結(jié)果更明顯,下次運行暫停的goroutine時從斷點處開始。
分析程序運行過程:
g1 讀取到 count 的值為 0;
然后 g1 暫停了,切換到 g2 運行,g2 讀取到 count 的值也為 0;
g2 暫停,切換到 g1暫停的位置繼續(xù)運行,g1 對 count+1,count 的值變?yōu)?1;
g1 暫停,切換到 g2,g2 剛剛已經(jīng)獲取到值 0,對其 +1,最后賦值給 count,其結(jié)果還是 1;
可以看出 g1 對 count+1 的結(jié)果被 g2 給覆蓋了,兩個 goroutine 都 +1 而結(jié)果還是 1。
通過上面的分析可以看出,之所以出現(xiàn)上面的問題,是因為兩個 goroutine 相互覆蓋結(jié)果。
所以我們對于同一個資源的讀寫必須是原子化的,也就是說,同一時間只能允許有一個 goroutine 對共享資源進(jìn)行讀寫操作。 此例子的共享資源就是count
通過go build -race生成一個可以執(zhí)行文件,然后再運行這個可執(zhí)行文件,就可以檢測資源競爭信息,看到打印出的檢測信息。如下
================== WARNING: DATA RACE Read at 0x000000619cbc by goroutine 8: main.incCount() D:/code/src/main.go:25 +0x80// goroutine 8 在代碼 25 行讀取共享資源value := count Previous write at 0x000000619cbc by goroutine 7: main.incCount() D:/code/src/main.go:28 +0x9f// goroutine 7 在代碼 28行修改共享資源count=value Goroutine 8 (running) created at: main.main() D:/code/src/main.go:17 +0x7e Goroutine 7 (finished) created at: main.main() D:/code/src/main.go:16 +0x66//兩個 goroutine 都是從 main 函數(shù)的 16、17 行通過 go 關(guān)鍵字啟動的。 ================== 4 Found 1 data race(s)
1.2 鎖住共享資源
Go語言提供了傳統(tǒng)的同步 goroutine 的機(jī)制,就是對共享資源加鎖。atomic 和 sync 包里的一些函數(shù)就可以對共享的資源進(jìn)行加鎖操作。
1.2.1 原子函數(shù)
原子函數(shù)能夠以很底層的加鎖機(jī)制來同步訪問整型變量和指針
import ( "fmt" "runtime" "sync" "sync/atomic" ) var ( counter int64 wg sync.WaitGroup ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() //等待goroutine結(jié)束 fmt.Println(counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { atomic.AddInt64(&counter, 1) //安全的對counter加1 runtime.Gosched() } }
上述代碼中使用了 atmoic 包的 AddInt64 函數(shù),這個函數(shù)會同步整型值的加法,方法是強(qiáng)制同一時刻只能有一個 gorountie 運行并完成這個加法操作。
另外兩個有用的原子函數(shù)是 LoadInt64 和 StoreInt64。這兩個函數(shù)提供了一種安全地讀和寫一個整型值的方式。下面的代碼就使用了 LoadInt64 和 StoreInt64 函數(shù)來創(chuàng)建一個同步標(biāo)志,這個標(biāo)志可以向程序里多個 goroutine 通知某個特殊狀態(tài)。
import ( "fmt" "sync" "sync/atomic" "time" ) var ( shutdown int64 wg sync.WaitGroup ) func main() { wg.Add(2) go doWork("A") go doWork("B") time.Sleep(1 * time.Second) fmt.Println("Shutdown Now") atomic.StoreInt64(&shutdown, 1) wg.Wait() } func doWork(name string) { 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 } } } --output-- Doing A Work Doing B Work Doing B Work Doing A Work Doing A Work Doing B Work Doing B Work Doing A Work//前8行順序每次運行時都不一樣 Shutdown Now Shutting A Down Shutting B Down//A和B都shut down后,由wg.Done()把計數(shù)器置0
上面代碼中 main 函數(shù)使用 StoreInt64 函數(shù)來安全地修改 shutdown 變量的值。如果哪個 doWork goroutine 試圖在 main 函數(shù)調(diào)用 StoreInt64 的同時調(diào)用 LoadInt64 函數(shù),那么原子函數(shù)會將這些調(diào)用互相同步,保證這些操作都是安全的,不會進(jìn)入競爭狀態(tài)。
1.2.2 鎖
見上篇文章,上面的例子為保持同步,取消競爭,可照以下操作:
func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { //同一時刻只允許一個goroutine進(jìn)入這個臨界區(qū) mutex.Lock() { value := counter runtime.Gosched()//退出當(dāng)前goroutine,調(diào)度器會再次分配這個 goroutine 繼續(xù)運行。 value++ counter = value } mutex.Unlock() //釋放鎖,允許其他正在等待的goroutine進(jìn)入臨界區(qū) } }
1.3 通道chan
統(tǒng)統(tǒng)將通道兩端的goroutine理解為生產(chǎn)者-消費者模式。
通道的數(shù)據(jù)接收一共有以下 4 種寫法。
阻塞接收數(shù)據(jù)
阻塞模式接收數(shù)據(jù)時,將接收變量作為<-操作符的左值,格式如下:
data := <-ch
執(zhí)行該語句時將會阻塞,直到接收到數(shù)據(jù)并賦值給 data 變量。
2) 非阻塞接收數(shù)據(jù)
使用非阻塞方式從通道接收數(shù)據(jù)時,語句不會發(fā)生阻塞,格式如下:
data, ok := <-ch
data:表示接收到的數(shù)據(jù)。未接收到數(shù)據(jù)時,data 為通道類型的零值。
ok:表示是否接收到數(shù)據(jù)。
非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少。如果需要實現(xiàn)接收超時檢測,可以配合 select 和計時器 channel 進(jìn)行
3) 循環(huán)接收數(shù)據(jù)
import ( "fmt" "time" ) func main() { // 構(gòu)建一個通道,這里有沒有緩沖都可,因為是收了就發(fā),無需阻塞等待 ch := make(chan int) // 開啟一個并發(fā)匿名函數(shù) go func() { // 從3循環(huán)到0 for i := 3; i >= 0; i-- { // 發(fā)送3到0之間的數(shù)值 ch <- i // 每次發(fā)送完時等待 time.Sleep(time.Second) } }() // 遍歷接收通道數(shù)據(jù) for data := range ch { // 打印通道數(shù)據(jù) fmt.Println(data) // 當(dāng)遇到數(shù)據(jù)0時, 退出接收循環(huán) if data == 0 { break } } } --output--
1.3.1 單向通道
ch := make(chan int) // 聲明一個只能寫入數(shù)據(jù)的通道類型, 并賦值為ch var chSendOnly chan<- int = ch 或 ch := make(chan<- int) //聲明一個只能讀取數(shù)據(jù)的通道類型, 并賦值為ch var chRecvOnly <-chan int = ch 或 ch := make(<-chan int)
1.3.2 優(yōu)雅的關(guān)閉通道
1.3.3 無緩沖的通道
如果兩個 goroutine 沒有同時準(zhǔn)備好,通道會導(dǎo)致先執(zhí)行發(fā)送或接收操作的 goroutine 阻塞等待。(阻塞指的是由于某種原因數(shù)據(jù)沒有到達(dá),當(dāng)前協(xié)程(線程)持續(xù)處于等待狀態(tài),直到條件滿足才解除阻塞)這種對通道進(jìn)行發(fā)送和接收的交互行為本身就是同步的。其中任意一個操作都無法離開另一個操作單獨存在。
在網(wǎng)球比賽中,兩位選手會把球在兩個人之間來回傳遞。選手總是處在以下兩種狀態(tài)之一,要么在等待接球,要么將球打向?qū)Ψ???梢允褂脙蓚€ goroutine 來模擬網(wǎng)球比賽,并使用無緩沖的通道來模擬球的來回
// 這個示例程序展示如何用無緩沖的通道來模擬 // 2 個goroutine 間的網(wǎng)球比賽 package main import ( "fmt" "math/rand" "sync" "time" ) // wg 用來等待程序結(jié)束 var wg sync.WaitGroup func init() { rand.Seed(time.Now().UnixNano()) } // main 是所有Go 程序的入口 func main() { // 創(chuàng)建一個無緩沖的通道 court := make(chan int) // 計數(shù)加 2,表示要等待兩個goroutine wg.Add(2) // 啟動兩個選手 go player("Nadal", court) go player("Djokovic", court) // 發(fā)球 court <- 1 // 等待游戲結(jié)束 wg.Wait() } // player 模擬一個選手在打網(wǎng)球 func player(name string, court chan int) { // 在函數(shù)退出時調(diào)用Done 來通知main 函數(shù)工作已經(jīng)完成 defer wg.Done() for { // 等待球被擊打過來 ball, ok := <-court if !ok { // 如果通道被關(guān)閉,我們就贏了 fmt.Printf("Player %s Won\n", name) return } // 選隨機(jī)數(shù),然后用這個數(shù)來判斷我們是否丟球 n := rand.Intn(100) if n%13 == 0 { fmt.Printf("Player %s Missed\n", name) // 關(guān)閉通道,表示我們輸了 close(court) return } // 顯示擊球數(shù),并將擊球數(shù)加1 fmt.Printf("Player %s Hit %d\n", name, ball) ball++ // 將球打向?qū)κ?,為啥這里是把ball發(fā)送到另一個go協(xié)程? //因為court無緩沖,此時另一個go協(xié)程正好在等待接收court內(nèi)的值,所以此時轉(zhuǎn)向另一個go協(xié)程代碼 court <- ball } }
1.3.4 有緩沖的通道
有緩沖的通道是一種在被接收前能存儲一個或者多個值的通道。這種類型的通道并不強(qiáng)制要求 goroutine 之間必須同時完成發(fā)送和接收,發(fā)送和接受的阻塞條件為只有在通道中沒有要接收的值時,接收動作才會阻塞。只有在通道沒有可用緩沖區(qū)容納被發(fā)送的值時,發(fā)送動作才會阻塞。
有緩沖的通道和無緩沖的通道之間的一個很大的不同:無緩沖的通道保證進(jìn)行發(fā)送和接收的 goroutine 會在同一時間進(jìn)行數(shù)據(jù)交換;有緩沖的通道沒有這種保證。
為什么要給通道限制緩沖區(qū)大小?
通道(channel)是在兩個 goroutine 間通信的橋梁。使用 goroutine 的代碼必然有一方提供數(shù)據(jù),一方消費數(shù)據(jù)。當(dāng)提供數(shù)據(jù)一方的數(shù)據(jù)供給速度大于消費方的數(shù)據(jù)處理速度時,如果通道不限制長度,那么內(nèi)存將不斷膨脹直到應(yīng)用崩潰。因此,限制通道的長度有利于約束數(shù)據(jù)提供方的供給速度,供給數(shù)據(jù)量必須在消費方處理量+通道長度的范圍內(nèi),才能正常地處理數(shù)據(jù)。
1.3.5 channel超時機(jī)制
select 機(jī)制不是專門為超時而設(shè)計的,卻能很方便的解決超時問題,因為 select 的特點是只要其中有一個 case 已經(jīng)完成,程序就會繼續(xù)往下執(zhí)行,而不會考慮其他 case 的情況。
基本語句為:
每個 case 語句里必須是一個 IO 操作,
select { case <-chan1: // 如果chan1成功讀到數(shù)據(jù),則進(jìn)行該case處理語句 case chan2 <- 1: // 如果成功向chan2寫入數(shù)據(jù),則進(jìn)行該case處理語句 default: // 如果上面都沒有成功,則進(jìn)入default處理流程 }
例子,注意之所以輸出5個num,是因為select里的time.After在這里的意思是ch通道無值可以接收的時候的3s后才print超時,即最多ch通道最多阻塞等待3s
func main() { ch := make(chan int) quit := make(chan bool) //新開一個協(xié)程 go func() { for { select { case num := <-ch: fmt.Println("num = ", num) case <-time.After(3 * time.Second): fmt.Println("超時") quit <- true } } }() //別忘了() for i := 0; i < 5; i++ { ch <- i time.Sleep(time.Second)//主協(xié)程進(jìn)入休眠狀態(tài),等待上面的go協(xié)程運行并進(jìn)入阻塞等待狀態(tài),就這樣來回運行,并通過chan通信 } <-quit fmt.Println("程序結(jié)束") } --output-- num = 0 num = 1 num = 2 num = 3 num = 4 超時 程序結(jié)束
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
golang?db事務(wù)的統(tǒng)一封裝的實現(xiàn)
這篇文章主要介紹了golang db事務(wù)的統(tǒng)一封裝的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12golang將切片或數(shù)組根據(jù)某個字段進(jìn)行分組操作
這篇文章主要介紹了golang將切片或數(shù)組根據(jù)某個字段進(jìn)行分組操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go語言defer與return執(zhí)行的先后順序詳解
這篇文章主要為大家介紹了Go語言defer與return執(zhí)行的先后順序詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12使用Golang實現(xiàn)對網(wǎng)絡(luò)數(shù)據(jù)包的捕獲與分析
在網(wǎng)絡(luò)通信中,網(wǎng)絡(luò)數(shù)據(jù)包是信息傳遞的基本單位,抓包是一種監(jiān)控和分析網(wǎng)絡(luò)流量的方法,用于獲取網(wǎng)絡(luò)數(shù)據(jù)包并對其進(jìn)行分析,本文將介紹如何使用Golang實現(xiàn)抓包功能,包括網(wǎng)絡(luò)數(shù)據(jù)包捕獲和數(shù)據(jù)包分析,需要的朋友可以參考下2023-11-11使用Go語言構(gòu)建高效的二叉搜索樹聯(lián)系簿
樹是一種重要的數(shù)據(jù)結(jié)構(gòu),而二叉搜索樹(BST)則是樹的一種常見形式,在本文中,我們將學(xué)習(xí)如何構(gòu)建一個高效的二叉搜索樹聯(lián)系簿,感興趣的可以了解下2024-01-01