詳解Go語言中如何通過Goroutine實(shí)現(xiàn)高并發(fā)
一、并發(fā)編程的基本概念
并發(fā)編程是指在一個(gè)程序中同時(shí)運(yùn)行多個(gè)任務(wù),這些任務(wù)可以獨(dú)立地執(zhí)行,也可以相互協(xié)作。并發(fā)編程可以提高程序的執(zhí)行效率,特別是在處理大量I/O操作或計(jì)算密集型任務(wù)時(shí)。
在Go語言中,并發(fā)編程主要通過goroutine和channel來實(shí)現(xiàn)。
Goroutine是Go語言獨(dú)有的并發(fā)執(zhí)行單元,它允許函數(shù)或方法并發(fā)執(zhí)行,而無需手動(dòng)管理線程。
Channel是Go語言中進(jìn)行g(shù)oroutine間通信和同步的主要機(jī)制。
二、進(jìn)程、線程、協(xié)程
**程序:**指令和數(shù)據(jù)的一個(gè)有序集合。本身沒有任何含義,是一個(gè)靜態(tài)的概念。
進(jìn)程:QQ.exe 微信 … 一個(gè)個(gè)的程序、執(zhí)行起來之后,開啟一個(gè)進(jìn)程。執(zhí)行程序的一次執(zhí)行過程,它是動(dòng)態(tài)的概念。進(jìn)程是系統(tǒng)資源分配的單位。
**線程:**一個(gè)進(jìn)程中可以有多個(gè)線程,并行的,一個(gè)進(jìn)程之中,至少要有一個(gè)線程。main 主線程
- 線程是CPU調(diào)度和執(zhí)行的單位。
- 一個(gè)線程,直接執(zhí)行就可以了
- 多個(gè)線程:CPU如何調(diào)度執(zhí)行。 一個(gè)CPU、也是可以跑多個(gè)線程的。
并行和并發(fā)
- 并發(fā):一個(gè)cpu同一時(shí)間不停執(zhí)行多個(gè)程序
- 并行:多個(gè)cpu同一時(shí)間不停執(zhí)行多個(gè)程序

在代碼級別中的所謂多線程并發(fā)處理問題。模擬出來的。
- 真正的多線程是指的擁有多個(gè)CPU、多核
- 如果是模擬出來的多線程、即在一個(gè)CPU的情況下,在同一個(gè)時(shí)間點(diǎn),只能執(zhí)行一個(gè)線程的代碼。
- 因?yàn)閳?zhí)行的速度很快,所以就有了同時(shí)在執(zhí)行的一種錯(cuò)覺。
并行真的就最快嗎?
- 并行運(yùn)行的組件,考慮到多個(gè)線程之間的通信問題,這種跨CPU通信問題是開銷比較高的,并行并不一定快。

進(jìn)程
進(jìn)程是一個(gè)程序在一個(gè)數(shù)據(jù)集中的一次動(dòng)態(tài)執(zhí)行過程,可以簡單理解為“正在執(zhí)行的程序",它是CPU資源分配和調(diào)度的獨(dú)立單位。
進(jìn)程一般由程序、數(shù)據(jù)集、進(jìn)程控制塊三部分組成。我們編寫的程序用來描述進(jìn)程要完成哪些功能以及如何完成;
數(shù)據(jù)集則是程序在執(zhí)行過程中所需要使用的資源;進(jìn)程控制塊用來記錄進(jìn)程的外部特征,描述進(jìn)程的執(zhí)行變化過程,系統(tǒng)可以利用它來控制和管理進(jìn)程,它是系統(tǒng)感知進(jìn)程存在的唯一標(biāo)志。 進(jìn)程的局限是創(chuàng)建、撤銷和切換的開銷比較大。
線程 :
線程是在進(jìn)程之后發(fā)展出來的概念。線程也叫輕量級進(jìn)程,它是一個(gè)基本的CPU執(zhí)行單元,也是程序執(zhí)行過程中的最小單元,由線程ID、 程序計(jì)數(shù)器、寄存器集合和堆棧共同組成。一個(gè)進(jìn)程可以包含多個(gè)線程。
線程的優(yōu)點(diǎn)是減小了程序并發(fā)執(zhí)行時(shí)的開銷,提高了操作系統(tǒng)的并發(fā)性能,**缺點(diǎn)是線程沒有自己的系統(tǒng)資源,同一進(jìn)程的各線程可以共享進(jìn)程所擁有的系統(tǒng)資源,如果把進(jìn)程比作一個(gè)車間,那么線程就好比是車間里面的工人。**不過對于某些獨(dú)占性資源存在鎖機(jī)制,處理不當(dāng)可能會(huì)產(chǎn)生”死鎖"。
協(xié)程Goroutine
協(xié)程是一種用戶態(tài)的輕量級線程,又稱微線程,英文名Coroutine,協(xié)程的調(diào)度完全由用戶控制。人們通常將協(xié)程和子程序(函數(shù))比較著理解。
就好比是啟動(dòng)了一個(gè)函數(shù),單次執(zhí)行完畢它。不影響我們main線程的執(zhí)行。
子程序調(diào)用總是一個(gè)入口,一次返回,一旦退出即完成了子程序的執(zhí)行。
與傳統(tǒng)的系統(tǒng)級線程和進(jìn)程相比,協(xié)程的最大優(yōu)勢在于其"輕量級”,可以輕松創(chuàng)建上百萬個(gè)而不會(huì)導(dǎo)致系統(tǒng)資源衰竭,而線程和進(jìn)程通常最多也不能超過1萬的。這也是協(xié)程也叫輕量級線程的原因。
補(bǔ)充點(diǎn):Go語言流行的主要一個(gè)原因,高并發(fā)的問題。高效!
Go語言對于并發(fā)的實(shí)現(xiàn)是靠協(xié)程,Goroutine
三、Goroutine
Go中使用Goroutine來實(shí)現(xiàn)并發(fā)concurrently
**Goroutine是Go語言特有的名詞。**區(qū)別于進(jìn)程Process,線程Thread, 協(xié)程Goroutine, 因?yàn)镚o語言的創(chuàng)造者們覺得和他們是有所區(qū)別的,所以專門創(chuàng)造了Goroutine
Goroutine是與其他函數(shù)或方法同時(shí)運(yùn)行的函數(shù)或方法。Goroutines可以被認(rèn)為是輕量級的線程。與線程相比,創(chuàng)建Goroutine的成本很小,它就是一段代碼,一個(gè)函數(shù)入口。以及在堆上為其分配的一個(gè)堆棧(初始大小為4K,會(huì)隨著程序的執(zhí)行自動(dòng)增長刪除)。因此它非常廉價(jià),Go應(yīng)用程序可以輕松并發(fā)運(yùn)行數(shù)千個(gè)Goroutines
在go語言中使用 goroutine,在調(diào)用函數(shù)或者方法前面加上 go 關(guān)鍵字即可。
普通方法調(diào)用 對比 多線程
**普通方法調(diào)用:**串行
main(){ // 串行執(zhí)行 1/2/3
test1()
test2()
test3()
}
多線程
main(){ // 4個(gè)線程同時(shí)執(zhí)行:main test1 test2 test3 、交替的快速執(zhí)行。
go test1()
go test2()
go test3()
}

四、Goroutine的創(chuàng)建和使用
Goroutine是由Go的運(yùn)行時(shí)調(diào)度和管理的輕量級線程。每個(gè)goroutine都有自己獨(dú)立的??臻g,并且由Go的運(yùn)行時(shí)環(huán)境進(jìn)行調(diào)度。
與線程不同,goroutine的創(chuàng)建和切換成本更低,因?yàn)樗皇呛瘮?shù)入口和堆棧的封裝,不需要像線程那樣進(jìn)行復(fù)雜的上下文切換。
1. 創(chuàng)建Goroutine
在Go語言中,使用go關(guān)鍵字來創(chuàng)建一個(gè)新的goroutine。例如:
package main
import (
"fmt"
"time"
)
func main() {
//使用go關(guān)鍵字來創(chuàng)建goroutine協(xié)程
go Hello()
time.Sleep(1 * time.Second) // 等待一秒鐘,確保Hello函數(shù)有足夠的時(shí)間執(zhí)行。如果不加等待,main函數(shù)如果結(jié)束了,所有的 goroutine也會(huì)瞬間銷毀
}
func Hello() {
fmt.Println("hello world")
}

2. Goroutine的規(guī)則
1、當(dāng)新的Goroutine開始時(shí), Goroutine調(diào)用立即返回。與函數(shù)不同,go不等待Goroutine執(zhí)行結(jié)束
2、當(dāng)Goroutine調(diào)用,并且Goroutine的任何返回值被忽略之后,go立即執(zhí)行到下一行代碼
3、main的Goroutine應(yīng)該為其他的Goroutines執(zhí)行。如果main的Goroutine終止了,程序?qū)⒈唤K止,而其他Goroutine將不會(huì)運(yùn)行
五、Goroutine的生命周期和調(diào)度
Go程序在一個(gè)主Goroutine中啟動(dòng),這個(gè)主Goroutine封裝了main函數(shù)。主Goroutine會(huì)進(jìn)行一系列的初始化工作,包括設(shè)置每個(gè)goroutine能申請的棧空間的最大尺寸、啟動(dòng)垃圾回收Goroutine等。
當(dāng)使用go關(guān)鍵字創(chuàng)建一個(gè)新的Goroutine時(shí),Go的運(yùn)行時(shí)會(huì)創(chuàng)建一個(gè)新的輕量級線程,并分配一個(gè)Goroutine棧,然后將該Goroutine添加到調(diào)度器中。
調(diào)度器會(huì)根據(jù)調(diào)度算法選擇一個(gè)可用的Goroutine運(yùn)行。如果當(dāng)前沒有可用的Goroutine,程序可能會(huì)進(jìn)入休眠狀態(tài)。
Goroutine在調(diào)用某些會(huì)引起阻塞的函數(shù)時(shí),會(huì)被暫停,直到函數(shù)返回結(jié)果。
這些函數(shù)包括I/O操作、網(wǎng)絡(luò)請求、系統(tǒng)調(diào)用、鎖等。
當(dāng)Goroutine阻塞時(shí),調(diào)度器會(huì)將它放回到隊(duì)列中,直到它再次準(zhǔn)備好運(yùn)行。
主Goroutine - mian
封裝main函數(shù)的goroutine稱為主goroutine。
主goroutine所做的事情并不是執(zhí)行main函數(shù)那么簡單。它首先要做的是:設(shè)定每一個(gè)goroutine所能申請的??臻g的最大尺寸。
在32位的計(jì)算機(jī)系統(tǒng)中此最大尺寸為250MB,而在64位的計(jì)算機(jī)系統(tǒng)中此尺寸為1GB。
如果有某個(gè)goroutine的棧空間尺寸大于這個(gè)限制,那么運(yùn)行時(shí)系統(tǒng)就會(huì)引發(fā)一個(gè)棧溢出(stack overflow)的運(yùn)行時(shí)恐慌。隨后,這個(gè)go程序的運(yùn)行也會(huì)終止。
此后,主goroutine會(huì) 進(jìn)行一系列的初始化工作,涉及的工作內(nèi)容大致如下:
1、創(chuàng)建一個(gè)特殊的defer語句,用于在主goroutine退出時(shí)做必要的善后處理。因?yàn)橹鱣oroutine也可能非正常的結(jié)束
2、啟動(dòng)專用于在后臺(tái)清掃內(nèi)存垃圾的goroutine,并設(shè)置GC可用的標(biāo)識(shí).
3、執(zhí)行main包中所引用包下的init函數(shù)
4、執(zhí)行main函數(shù)
執(zhí)行完main函數(shù)后,它還會(huì)檢查主goroutine是否引發(fā)了運(yùn)行時(shí)恐慌,并進(jìn)行必要的處理。
程序運(yùn)行完畢后,主goroutine會(huì)結(jié)束自己以及當(dāng)前進(jìn)程的運(yùn)行。
六、使用runtime包管理Goroutine
Go語言的runtime包提供了與Go運(yùn)行時(shí)環(huán)境交互的各種功能,包括垃圾回收、并發(fā)控制、程序退出、堆棧管理等。以下是一些常用的runtime包函數(shù):
- 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:為對象設(shè)置終結(jié)器,當(dāng)垃圾回收器準(zhǔn)備回收該對象時(shí),會(huì)調(diào)用該終結(jié)器。
- runtime.GC:強(qiáng)制運(yùn)行垃圾回收器。
- runtime.GOOS: 獲取操作系統(tǒng)名稱
這些函數(shù)可以幫助我們更好地管理Goroutine和并發(fā)編程。
例如,可以使用runtime.GOMAXPROCS來設(shè)置最大可運(yùn)行的操作系統(tǒng)線程數(shù),從而控制并發(fā)度。
可以使用runtime.NumGoroutine來監(jiān)控當(dāng)前運(yùn)行的Goroutine數(shù)量,以便進(jìn)行性能調(diào)優(yōu)。
獲取系統(tǒng)的信息runtime
package main
import (
"fmt"
"runtime"
)
// 獲取系統(tǒng)的信息runtime
func main() {
// 獲取goRoot目錄 : 找到指定目錄,存放一些項(xiàng)目信息。
fmt.Println("GoRoot Path:", runtime.GOROOT())
// 獲取操作系統(tǒng) windows ,可以根據(jù)操作系統(tǒng)類型判斷盤符分隔符。 “\\” “/”
fmt.Println("System:", runtime.GOOS)
// 獲取cpu數(shù)量 12, 可以嘗試做一些系統(tǒng)優(yōu)化,開啟更大的??臻g。
fmt.Println("Cpu num:", runtime.NumCPU())
}

并發(fā)控制
package main
import (
"fmt"
"runtime"
)
// 控制并發(fā)順序
func main() {
// goroutine是競爭cpu的 ,調(diào)度
go func() {
for i := 0; i < 5; i++ {
fmt.Println("goroutine", i)
}
}()
for i := 0; i < 5; i++ {
// gosched:禮讓, 讓出時(shí)間片,讓其他的 goroutine 先執(zhí)行
// cpu是隨機(jī),相對來說,可以讓一下,但是不一定能夠成功
// schedule
runtime.Gosched()
fmt.Println("main-", i)
}
}
正常情況下,main主函數(shù)里面的協(xié)程一般是先于go func()執(zhí)行的,但是我們在main函數(shù)里面加上了runtime.Gosched(),這樣main函數(shù)里面的代碼執(zhí)行就讓出CPU時(shí)間片,讓其他Goroutine先執(zhí)行

Goroutine的終止和清理
使用runtime.Goexit()終止Goroutine
runtime.Goexit()函數(shù)用于終止當(dāng)前Goroutine的執(zhí)行。當(dāng)調(diào)用Goexit()時(shí),當(dāng)前Goroutine會(huì)立即停止執(zhí)行,并把控制權(quán)交還給調(diào)度器。
這意味著當(dāng)前Goroutine不會(huì)繼續(xù)執(zhí)行后續(xù)的代碼,也不會(huì)返回到調(diào)用它的地方。同時(shí),其他仍在運(yùn)行的Goroutine將繼續(xù)執(zhí)行。例如:
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
//return // 只是終止了函數(shù)
//這里設(shè)置,終止當(dāng)前的 goroutine,該Goroutine中下面的代碼不再執(zhí)行
runtime.Goexit()
defer fmt.Println("C.defer")
fmt.Println("B")
}()
fmt.Println("A")
}()
time.Sleep(1 * time.Second)
}

七、多線程會(huì)遇到的問題
1. 臨界資源的安全問題
臨界資源:指并發(fā)環(huán)境中多個(gè)進(jìn)程、線程、協(xié)程共享的資源
在并發(fā)編程中對臨界資源的處理不當(dāng),往往會(huì)導(dǎo)致數(shù)據(jù)不一致的問題。
package main
import (
"fmt"
"time"
)
func main() {
// 此時(shí)的a就是臨界資源:多個(gè)協(xié)程共享的變量,會(huì)導(dǎo)致程序結(jié)果未知
a := 1
go func() {
a = 2
fmt.Println("goroutine a:", a)
}()
a = 3
time.Sleep(3 * time.Second)
fmt.Println("main a:", a)
}

2. 經(jīng)典案例:售票問題
并發(fā)本身并不復(fù)雜,但是因?yàn)橛辛速Y源競爭的問題,就使得我們開發(fā)出好的并發(fā)程序變得復(fù)雜起來,因?yàn)闀?huì)引起很多莫名其妙的問題。
如果多個(gè)goroutine在訪問同一個(gè)數(shù)據(jù)資源的時(shí)候,其中一個(gè)線程修改了數(shù)據(jù),那么這個(gè)數(shù)值就被修改了,對于其他的goroutine來講,這個(gè)數(shù)值可能是不對的。
package main
import (
"fmt"
"time"
)
// 定義全局變量 票庫存為10張
var ticket int = 10
func main() {
// 單線程不存在問題,多線程資源爭搶就出現(xiàn)了問題
//多人搶票
go saleTickets("張三")
go saleTickets("李四")
go saleTickets("王五")
go saleTickets("趙六")
time.Sleep(time.Second * 5)
}
// 售票函數(shù)
func saleTickets(name string) {
for {
if ticket > 0 {
time.Sleep(time.Millisecond * 1)
fmt.Println(name, "剩余票的數(shù)量為:", ticket)
//每賣出一張票,票的數(shù)量減一
ticket--
} else {
fmt.Println("票已售完")
break
}
}
}
多線程都在操作數(shù)據(jù),出現(xiàn)了負(fù)數(shù)這種不合理的結(jié)果

發(fā)現(xiàn)結(jié)果和預(yù)想的不同,多線程加入之后,原先單線程的邏輯出現(xiàn)了問題。
以上就是詳解Go語言中如何通過Goroutine實(shí)現(xiàn)高并發(fā)的詳細(xì)內(nèi)容,更多關(guān)于Go Goroutine實(shí)現(xiàn)高并發(fā)的資料請關(guān)注腳本之家其它相關(guān)文章!
- Golang 語言控制并發(fā) Goroutine的方法
- Go并發(fā)編程之goroutine使用正確方法
- Go并發(fā)的方法之goroutine模型與調(diào)度策略
- Go語言中的并發(fā)goroutine底層原理
- Go語言使用goroutine及通道實(shí)現(xiàn)并發(fā)詳解
- GoLang并發(fā)機(jī)制探究goroutine原理詳細(xì)講解
- Golang并發(fā)繞不開的重要組件之Goroutine詳解
- Go中Goroutines輕量級并發(fā)的特性及效率探究
- golang并發(fā)編程中Goroutine 協(xié)程的實(shí)現(xiàn)
- Go 并發(fā)編程Goroutine的實(shí)現(xiàn)示例
相關(guān)文章
Go語言使用templ實(shí)現(xiàn)編寫HTML用戶界面
templ是一個(gè)在 Go 中編寫 HTML 用戶界面的語言,使用 templ,我們可以創(chuàng)建可呈現(xiàn) HTML 片段的組件,下面就跟隨小編一起了解一下具體的實(shí)現(xiàn)方法吧2023-12-12
golang 兩個(gè)go程輪流打印一個(gè)切片的實(shí)現(xiàn)
這篇文章主要介紹了golang 兩個(gè)go程輪流打印一個(gè)切片的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
go語言中的json與map相互轉(zhuǎn)換實(shí)現(xiàn)
本文主要介紹了go語言中的json與map相互轉(zhuǎn)換實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Go語言模擬while語句實(shí)現(xiàn)無限循環(huán)的方法
這篇文章主要介紹了Go語言模擬while語句實(shí)現(xiàn)無限循環(huán)的方法,實(shí)例分析了for語句模擬while語句的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02
Golang無限緩存channel的設(shè)計(jì)與實(shí)現(xiàn)解析
這篇文章主要為大家介紹了Golang無限緩存channel的設(shè)計(jì)與實(shí)現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07

