Go 并發(fā)編程Goroutine的實(shí)現(xiàn)示例
進(jìn)程(Process),線程(Thread),協(xié)程(Goroutine,也叫輕量級(jí)線程)
進(jìn)程
進(jìn)程是一個(gè)程序在一個(gè)數(shù)據(jù)集中的一次動(dòng)態(tài)執(zhí)行過(guò)程,進(jìn)程一般由程序,數(shù)據(jù)集,進(jìn)程控制塊三部分組成
線程
線程也叫輕量級(jí)進(jìn)程,他是一個(gè)基本的CPU執(zhí)行單元,也就是程序執(zhí)行過(guò)程中的最小單元,由線程ID,程序計(jì)數(shù)器,寄存器集合和堆棧共同組成的,一個(gè)進(jìn)程可以包含多個(gè)線程
協(xié)程
協(xié)程是一種用戶態(tài)的輕量級(jí)線程,又稱微線程,協(xié)程的調(diào)度完全由用戶控制
一、主Goroutine
封裝main函數(shù)的Goroutine被稱為主Goroutine
主Goroutine所做的事情并不是執(zhí)行main函數(shù)那么簡(jiǎn)單,它首先要做的是設(shè)定每一個(gè)goroutine所能申請(qǐng)的??臻g的最大尺寸,在32位計(jì)算機(jī)系統(tǒng)中此最大尺寸為250MB,而在64位計(jì)算機(jī)系統(tǒng)中此尺寸為1GB,如果有某個(gè)Goroutine的??臻g尺寸大于這個(gè)限制,那么運(yùn)行時(shí)系統(tǒng)就會(huì)引發(fā)一個(gè)棧溢出(stack overflow)的運(yùn)行時(shí)恐慌,隨后這個(gè)go程序的運(yùn)行也會(huì)終止
此后主Goroutine會(huì)進(jìn)行一系列的初始化工作:
1、創(chuàng)建一個(gè)特殊的defer語(yǔ)句,用于在主Goroutine退出時(shí)做必要的善后處理,因?yàn)橹鱃oroutine也可能非正常結(jié)束
2、啟動(dòng)專用于在后臺(tái)清掃內(nèi)存垃圾的Goroutine,并設(shè)置GC可用的表示
3、執(zhí)行main包中所引用包的init函數(shù)
4、執(zhí)行main函數(shù)
二、Goroutine
GO中使用Goroutine來(lái)實(shí)現(xiàn)并發(fā)
Goroutine是與其他函數(shù)或方法同時(shí)運(yùn)行的函數(shù)或方法,與線程相比創(chuàng)建goroutine的成本很小,他就是一段代碼,一個(gè)函數(shù)入口,以及在堆上為其分配一個(gè)堆棧(初始大小為4k,會(huì)隨著程序的執(zhí)行自動(dòng)增長(zhǎng)刪除)。
在GO語(yǔ)言中使用goroutine,在調(diào)用函數(shù)或者方法前面加上go關(guān)鍵字即可
package main import "fmt" func main() { // 使用go關(guān)鍵字使用goroutine調(diào)用hello函數(shù) go hello() for i := 0; i < 150000; i++ { //fmt.Println("main-", i) } } func hello() { for i := 0; i < 10; i++ { fmt.Println("hello-----------", i) } } /* 此處代碼可設(shè)置main協(xié)程for循環(huán)次數(shù)的大小觀測(cè)go協(xié)程調(diào)用hello情況 */
當(dāng)新的Goroutine開(kāi)始時(shí),Goroutine調(diào)用立即返回,與函數(shù)不同,go不等待Goroutine執(zhí)行結(jié)束
當(dāng)Goroutine調(diào)用,并且Goroutine的任何返回值被忽略之后,go立即執(zhí)行到下一行代碼
mian的Goroutine應(yīng)該為其他的Goroutine執(zhí)行,如果main的Goroutine終止了,程序?qū)⒈唤K止,而其他的Goroutine將不會(huì)運(yùn)行
三、runtime
獲取系統(tǒng)信息
schedule調(diào)度讓出時(shí)間片,讓別的goroutine先執(zhí)行
Goexit //終止當(dāng)前的goroutine
Go 語(yǔ)言的 runtime
包提供了與 Go 運(yùn)行時(shí)環(huán)境交互的各種功能。這個(gè)包允許你控制和檢查程序的運(yùn)行時(shí)行為,包括但不限于:
垃圾回收(Garbage Collection):可以手動(dòng)觸發(fā)垃圾回收,或者調(diào)整垃圾回收的策略。
并發(fā)控制:提供了包括
Gosched()
在內(nèi)的方法來(lái)控制 goroutine 的調(diào)度。程序退出:可以正常或非正常地退出程序。
堆棧管理:可以獲取當(dāng)前 goroutine 的堆棧信息。
環(huán)境變量:讀取和設(shè)置環(huán)境變量。
系統(tǒng)信號(hào):處理操作系統(tǒng)信號(hào)。
CPU 信息:獲取 CPU 的數(shù)量和相關(guān)信息。
內(nèi)存分配:可以手動(dòng)分配和釋放內(nèi)存。
性能監(jiān)控:可以監(jiān)控程序的 CPU 使用情況。
以下是一些 runtime
包中常用函數(shù)的簡(jiǎn)要說(shuō)明:
runtime.GOMAXPROCS
:設(shè)置最大可運(yùn)行的操作系統(tǒng)線程數(shù)。runtime.NumCPU
:返回機(jī)器的 CPU 核心數(shù)。runtime.NumGoroutine
:返回當(dāng)前運(yùn)行的 goroutine 數(shù)量。runtime.Gosched
:讓出 CPU 時(shí)間片,使得其他 goroutine 可以運(yùn)行。runtime.Goexit
:退出當(dāng)前的 goroutine。runtime.KeepAlive
:確保某個(gè) goroutine 不會(huì)被垃圾回收。runtime.SetFinalizer
:為對(duì)象設(shè)置終結(jié)器,當(dāng)垃圾回收器準(zhǔn)備回收該對(duì)象時(shí),會(huì)調(diào)用該終結(jié)器。runtime.GC
:強(qiáng)制運(yùn)行垃圾回收器。
package main import ( "fmt" "runtime" ) func main() { //獲取系統(tǒng)信息 fmt.Println("獲取GOROOT目錄", runtime.GOROOT()) fmt.Println("獲取操作系統(tǒng)", runtime.GOOS) fmt.Println("獲取CPU", runtime.NumCPU()) //Goroutine 調(diào)度 go func() { for i := 0; i < 100; i++ { fmt.Println("Goroutine---", i) } }() for i := 0; i < 100; i++ { //讓出時(shí)間片,讓別的Goroutine先執(zhí)行,不一定可以讓成功 runtime.Gosched() fmt.Println("main---", i) } }
runtime.Gosched()
是 Go 語(yǔ)言運(yùn)行時(shí)庫(kù)中的一個(gè)函數(shù),它用于讓出 CPU 時(shí)間片,讓其他 goroutine(輕量級(jí)線程)有機(jī)會(huì)執(zhí)行。這通常用于避免阻塞或減少阻塞的持續(xù)時(shí)間,尤其是在長(zhǎng)時(shí)間運(yùn)行的 goroutine 中,你可能會(huì)在適當(dāng)?shù)牡胤秸{(diào)用 Gosched
來(lái)讓出 CPU,以避免長(zhǎng)時(shí)間占用 CPU 導(dǎo)致其他 goroutine 饑餓。
以下是 runtime.Gosched()
函數(shù)的一些使用場(chǎng)景:
避免饑餓:在長(zhǎng)時(shí)間運(yùn)行的循環(huán)中,如果確定當(dāng)前 goroutine 可能不會(huì)被阻塞,可以調(diào)用
Gosched
來(lái)讓出 CPU。控制執(zhí)行順序:在某些情況下,你可能希望控制 goroutine 的執(zhí)行順序,通過(guò)
Gosched
可以給其他 goroutine 運(yùn)行的機(jī)會(huì)。減少 CPU 使用:在某些 I/O 密集型操作中,如果當(dāng)前 goroutine 主要是等待 I/O 操作完成,調(diào)用
Gosched
可以讓出 CPU,減少不必要的 CPU 使用。避免死鎖:在某些復(fù)雜的 goroutine 調(diào)度中,如果擔(dān)心死鎖問(wèn)題,可以在適當(dāng)?shù)牡胤秸{(diào)用
Gosched
來(lái)減少死鎖的風(fēng)險(xiǎn)。
package main import ( "fmt" "runtime" "time" ) func main() { /* 因?yàn)間oroutine2延時(shí)了一定時(shí)間,如果goroutine1不讓出CPU時(shí)間片那么必先執(zhí)行完成 */ go func() { for i := 0; i < 5; i++ { runtime.Gosched() // 讓出 CPU 時(shí)間片 fmt.Println("Goroutine 1:", i) } }() go func() { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println("Goroutine 2:", i) } }() time.Sleep(3 * time.Second) // 等待兩個(gè) goroutine 執(zhí)行完畢 }
請(qǐng)注意,過(guò)度使用 Gosched
可能會(huì)導(dǎo)致性能下降,因?yàn)轭l繁的調(diào)度會(huì)消耗額外的 CPU 資源。因此,應(yīng)該在仔細(xì)考慮后,根據(jù)實(shí)際需要來(lái)使用 Gosched
。
查看協(xié)程數(shù)&CUP數(shù)
package main import ( "fmt" "runtime" "sync" ) func main() { var wg sync.WaitGroup //創(chuàng)建并發(fā)組 fmt.Printf("當(dāng)前運(yùn)行的goroutine數(shù)量: %d\n", runtime.NumGoroutine()) //創(chuàng)建10個(gè)協(xié)程 for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() fmt.Printf("第 %d 個(gè)協(xié)程在 running\n", id) }(i) } fmt.Printf("當(dāng)前運(yùn)行的goroutine數(shù)量: %d\n", runtime.NumGoroutine()) wg.Wait() fmt.Printf("CPU核心數(shù): %d\n", runtime.NumCPU()) }
四、互斥鎖
在并發(fā)編程中會(huì)遇到的臨界資源安全問(wèn)題,可以采用互斥鎖的方式來(lái)解決,后面也可通過(guò)通過(guò)通道channel來(lái)解決
臨界資源:指并發(fā)環(huán)境中多個(gè)進(jìn)程、線程、協(xié)程共享的資源
使用sync包下的鎖解決臨界資源安全問(wèn)題(Mutex)
package main import ( "fmt" "sync" "time" ) // 定義全局變量 票庫(kù)存為10張 var tickets int = 10 // 創(chuàng)建鎖 var mutexs sync.Mutex func main() { //三個(gè)窗口同時(shí)售票 go saleTicket("售票口1") go saleTicket("售票口2") go saleTicket("售票口3") time.Sleep(time.Second * 15) //等待售票完 } // 售票函數(shù) func saleTicket(name string) { for { // 在檢查之前上鎖 mutexs.Lock() if tickets > 0 { time.Sleep(time.Second) fmt.Printf("%s剩余的票數(shù)為:%d\n", name, tickets) tickets-- } else { fmt.Println("票已售完") break } //操作結(jié)束后解鎖 mutexs.Unlock() } }
sync包下的同步等待組(WaitGroup)
package main import ( "fmt" "sync" "time" ) var w sync.WaitGroup func main() { // 公司最后關(guān)門(mén)的人 0 // wg.Add(2) 判斷還有幾個(gè)線程、計(jì)數(shù) num=2 // wg.Done() 我告知我已經(jīng)結(jié)束了 -1 w.Add(2) go test11() go test22() fmt.Println("main等待ing") w.Wait() // 等待 wg 歸零,才會(huì)繼續(xù)向下執(zhí)行 fmt.Println("end") // 理想狀態(tài):所有協(xié)程執(zhí)行完畢之后,自動(dòng)停止。 //time.Sleep(3 * time.Second) } func test11() { for i := 0; i < 5; i++ { time.Sleep(1 * time.Second) fmt.Println("test1--", i) } w.Done() } func test22() { defer w.Done() for i := 0; i < 5; i++ { fmt.Println("test2--", i) } }
五、Channel通道
不要以共享內(nèi)存的方式通信,而要以通信的方式共享內(nèi)存
通道可以被認(rèn)為是Goroutines通信的管道,類(lèi)似于管道中的水從一端到另一端的流動(dòng),數(shù)據(jù)可以從一端發(fā)送到另一端,通過(guò)通道接收,GO語(yǔ)言中建議使用Channel通道來(lái)實(shí)現(xiàn)Goroutines之間的通信
GO從語(yǔ)言層面保證同一個(gè)時(shí)間只有一個(gè)goroutine能夠訪問(wèn)channel里面的數(shù)據(jù),使用channel來(lái)通信,通過(guò)通信來(lái)傳遞內(nèi)存數(shù)據(jù),使得內(nèi)存數(shù)據(jù)在不同的goroutine中傳遞,而不是使用共享內(nèi)存來(lái)通信
每個(gè)通道都有與其相關(guān)的類(lèi)型,類(lèi)型是通道允許傳輸?shù)臄?shù)據(jù)類(lèi)型(通道的零值為nil,nil通道沒(méi)有任何用處,因此通道必須使用類(lèi)似于map和切片的方法定義)
一個(gè)通道發(fā)送和接收數(shù)據(jù)默認(rèn)是阻塞的,當(dāng)一個(gè)數(shù)據(jù)被發(fā)送到通道時(shí),在發(fā)送語(yǔ)句中被阻塞,直到另一個(gè)Goroutine從通道中讀取數(shù)據(jù)
關(guān)閉通道
發(fā)送者可以通過(guò)關(guān)閉通道來(lái)通知接收方不會(huì)有更多的數(shù)據(jù)被發(fā)送到通道
close(ch)
接收者可以在接收來(lái)自通道的數(shù)據(jù)時(shí)使用額外的變量來(lái)檢查通道是否已關(guān)閉
v,ok := <- ch
當(dāng)ok的值為true,表示成功的從通道中讀取了一個(gè)數(shù)據(jù)value,通道關(guān)閉時(shí)仍然可以讀(存)數(shù)據(jù)當(dāng)ok的值為false,表示從一個(gè)封閉的通道讀取數(shù)據(jù),從閉通道讀取的數(shù)據(jù)將是通道類(lèi)型的零值
緩沖通道
緩沖通道是指一個(gè)通道,帶有一個(gè)緩沖區(qū),發(fā)送到一個(gè)緩沖通道只有在緩沖區(qū)滿時(shí)才被阻塞,類(lèi)似的,從緩沖通道接收的信息只有在為空時(shí)才會(huì)被阻塞,可以通過(guò)將額外的容量參數(shù)傳遞給make函數(shù)來(lái)創(chuàng)建緩沖通道,該函數(shù)指定緩沖區(qū)的大小
package main import ( "fmt" "strconv" "time" ) func main() { //定義通道可以寫(xiě)10個(gè)數(shù)據(jù) ch := make(chan string, 10) go test3(ch) for v := range ch { fmt.Println(v) } } func test3(ch chan string) { for i := 0; i < 5; i++ { time.Sleep(time.Second) fmt.Println("通道內(nèi)寫(xiě)入數(shù)據(jù)", "tset--"+strconv.Itoa(i)) ch <- "tset--" + strconv.Itoa(i) } close(ch)//如果不關(guān)閉協(xié)程,主協(xié)程的for循環(huán)一值阻塞,知道報(bào)錯(cuò)“fatal error: all goroutines are asleep - deadlock!” }
定向通道
單向通道也就是定向通道,這些通道只能發(fā)送數(shù)據(jù)或者接收數(shù)據(jù)
package main import ( "fmt" "time" ) func main() { ch := make(chan int) go writerOnly(ch) go readOnly(ch) time.Sleep(time.Second * 2) } // 只讀,指只允許管道讀入/寫(xiě)出數(shù)據(jù) func writerOnly(ch chan<- int) { ch <- 10 } // 只寫(xiě),指指只允許管道讀出/寫(xiě)入數(shù)據(jù) func readOnly(ch <-chan int) { temp := <-ch fmt.Println(temp) }
Select
每個(gè)case都必須是一個(gè)通道的操作
如果任意某個(gè)通信可以進(jìn)行,他就執(zhí)行,其他被忽略
如果有多個(gè)case都可以運(yùn)行,Select會(huì)隨機(jī)公平的選出一個(gè)執(zhí)行
否則
如果有default子句,則執(zhí)行該語(yǔ)句
如果沒(méi)有default字句,select將阻塞,直到某個(gè)通信可以運(yùn)行,GO不會(huì)重新對(duì)channel或值進(jìn)行求值
package main import ( "fmt" "time" ) func main() { ch1 := make(chan int) ch2 := make(chan int) go func() { time.Sleep(time.Second * 2) ch1 <- 100 }() go func() { time.Sleep(time.Second * 2) ch2 <- 200 }() select { case num1 := <-ch1: fmt.Println("ch1--", num1) case num2 := <-ch2: fmt.Println("ch2--", num2) } //沒(méi)有default,則等待通道(阻塞),因?yàn)閟elect{}本身是阻塞的 }
利用通道解決臨界資源安全問(wèn)題
package main import ( "fmt" "sync" "time" ) // 定義全局chan,存儲(chǔ)票總數(shù) var totalTickets chan int var wg sync.WaitGroup func main() { // 初始化票數(shù)量:總票數(shù)10張 totalTickets = make(chan int, 2) totalTickets <- 10 wg.Add(3) go sell("售票口1") go sell("售票口2") go sell("售票口3") wg.Wait() fmt.Println("買(mǎi)完了,下班") } func sell(name string) { defer wg.Done() for { //for循環(huán)表示一直在賣(mài),一直在營(yíng)業(yè) residue, ok := <-totalTickets if !ok { fmt.Printf("%s: 關(guān)閉\n", name) break } if residue > 0 { time.Sleep(time.Second * 1) totalTickets <- residue - 1 // fmt.Println(name, "售出1張票,余票:", residue) } else { //進(jìn)入此處時(shí)票已經(jīng)買(mǎi)完了,因?yàn)閒or循環(huán)一進(jìn)來(lái)售票窗口就檢查是否還有票,假如最后賣(mài)完的是售票口3,那么銷(xiāo)售窗口1跟2就會(huì)判斷還有沒(méi)有 fmt.Printf("%s: 關(guān)閉\n", name) close(totalTickets) break } } }
到此這篇關(guān)于Go 并發(fā)編程Goroutine的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Go 并發(fā)Goroutine內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Golang 語(yǔ)言控制并發(fā) Goroutine的方法
- Go并發(fā)編程之goroutine使用正確方法
- Go并發(fā)的方法之goroutine模型與調(diào)度策略
- Go語(yǔ)言中的并發(fā)goroutine底層原理
- Go語(yǔ)言使用goroutine及通道實(shí)現(xiàn)并發(fā)詳解
- GoLang并發(fā)機(jī)制探究goroutine原理詳細(xì)講解
- Golang并發(fā)繞不開(kāi)的重要組件之Goroutine詳解
- Go中Goroutines輕量級(jí)并發(fā)的特性及效率探究
- 詳解Go語(yǔ)言中如何通過(guò)Goroutine實(shí)現(xiàn)高并發(fā)
- golang并發(fā)編程中Goroutine 協(xié)程的實(shí)現(xiàn)
相關(guān)文章
一步步教你在Linux上安裝Go語(yǔ)言環(huán)境
本文將介紹如何在Linux操作系統(tǒng)下搭建Go語(yǔ)言環(huán)境,Go語(yǔ)言是一種開(kāi)源的編程語(yǔ)言,具有高效、簡(jiǎn)潔和并發(fā)性強(qiáng)的特點(diǎn),適用于開(kāi)發(fā)各種類(lèi)型的應(yīng)用程序,搭建Go語(yǔ)言環(huán)境是開(kāi)始學(xué)習(xí)和開(kāi)發(fā)Go語(yǔ)言項(xiàng)目的第一步,本文將詳細(xì)介紹安裝Go語(yǔ)言、配置環(huán)境變量以及驗(yàn)證安裝是否成功的步驟2023-10-10Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解
這篇文章主要介紹了Golang中優(yōu)秀的消息隊(duì)列NSQ基礎(chǔ)安裝及使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go語(yǔ)言實(shí)現(xiàn)websocket推送程序
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)websocket推送程序,WebSocket是基于TCP的一個(gè)雙向傳輸數(shù)據(jù)的協(xié)議,和HTTP協(xié)議一樣,是在應(yīng)用層的,他的出現(xiàn),是為了解決網(wǎng)頁(yè)進(jìn)行持久雙向傳輸數(shù)據(jù)的問(wèn)題2023-01-01golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例
這篇文章主要為大家介紹了golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04