Go語言并發(fā)處理的使用詳解
并發(fā)是 Go 語言最核心、最亮眼的特性,Go 從語言層面原生支持并發(fā),不像 Java/Python 等語言需要依賴線程庫、協(xié)程框架,它的并發(fā)設(shè)計簡潔、輕量、高效,也是 Go 能在后端開發(fā) / 云原生領(lǐng)域脫穎而出的核心原因。
一、Go 并發(fā)的核心思想:不要通過共享內(nèi)存來通信,要通過通信來共享內(nèi)存
這是 Go 語言之父 Rob Pike 提出的并發(fā)黃金法則,也是 Go 并發(fā)設(shè)計的底層邏輯:
- 傳統(tǒng)語言(Java/C++)并發(fā):多線程搶占共享內(nèi)存,通過
鎖(synchronized/lock)保證數(shù)據(jù)安全,容易出現(xiàn)死鎖、競態(tài)、鎖爭用等問題; - Go 語言并發(fā):goroutine 之間不推薦共享內(nèi)存,而是通過專門的
channel管道傳遞數(shù)據(jù),把數(shù)據(jù)所有權(quán)從一個 goroutine 轉(zhuǎn)移到另一個,天然避免數(shù)據(jù)競爭,代碼更安全、簡潔。
二、Go 并發(fā)的兩大基石
Go 的并發(fā)能力完全依賴兩個核心組件實現(xiàn),二者相輔相成,缺一不可:goroutine(協(xié)程) + channel(通道)
1. Goroutine (輕量級協(xié)程) - Go 并發(fā)的執(zhí)行體
(1)什么是 Goroutine
goroutine 是 Go 語言內(nèi)置的、輕量級的執(zhí)行單元,也叫「用戶級線程 / 協(xié)程」,它由 Go 運行時(runtime) 調(diào)度,而不是操作系統(tǒng)內(nèi)核調(diào)度。
(2)Goroutine 核心特點
- 極致輕量:一個 goroutine 的初始??臻g僅 2KB,并且棧空間可以動態(tài)擴容 / 縮容(最大幾 MB),完全由 runtime 自動管理;
- 資源占用極少:一臺普通服務(wù)器上,輕松創(chuàng)建 10 萬、甚至百萬級別的 goroutine,不會有內(nèi)存壓力;而操作系統(tǒng)線程(thread)的棧空間默認(rèn)是 1MB,創(chuàng)建幾千個線程就會內(nèi)存溢出;
- 調(diào)度效率極高:Go 的 M/P/G 調(diào)度模型,能把 goroutine 高效映射到操作系統(tǒng)線程,調(diào)度切換的開銷只有線程的幾百分之一;
- goroutine 是并發(fā)執(zhí)行:多個 goroutine 在同一個 Go 程序中,宏觀上 “同時運行”,微觀上由 runtime 做時間片輪轉(zhuǎn)調(diào)度。
(3)如何創(chuàng)建 Goroutine
語法超級簡單:在函數(shù)調(diào)用前加一個 go 關(guān)鍵字 即可,這個函數(shù)就會開啟一個新的 goroutine 執(zhí)行。
package main
import "fmt"
// 定義一個普通函數(shù)
func printNum(name string) {
for i := 1; i <= 3; i++ {
fmt.Printf("協(xié)程[%s]:輸出數(shù)字 %d\n", name, i)
}
}
func main() {
// 1. 開啟第一個goroutine執(zhí)行printNum
go printNum("goroutine-1")
// 2. 開啟第二個goroutine執(zhí)行printNum
go printNum("goroutine-2")
// 注意:主goroutine執(zhí)行完會直接退出,導(dǎo)致子goroutine來不及執(zhí)行
// 這里簡單休眠1秒,等子goroutine執(zhí)行完畢(僅演示,生產(chǎn)不用這個方式)
fmt.Println("主協(xié)程開始等待...")
import "time" // 實際代碼要把import寫在頂部
time.Sleep(time.Second)
fmt.Println("主協(xié)程執(zhí)行結(jié)束")
}執(zhí)行結(jié)果(順序不固定,因為 goroutine 并發(fā)調(diào)度):
主協(xié)程開始等待...
協(xié)程[goroutine-1]:輸出數(shù)字 1
協(xié)程[goroutine-2]:輸出數(shù)字 1
協(xié)程[goroutine-1]:輸出數(shù)字 2
協(xié)程[goroutine-2]:輸出數(shù)字 2
協(xié)程[goroutine-1]:輸出數(shù)字 3
協(xié)程[goroutine-2]:輸出數(shù)字 3
主協(xié)程執(zhí)行結(jié)束
(4)補充:主 Goroutine
- 每個 Go 程序啟動后,會自動創(chuàng)建一個主 goroutine,執(zhí)行
main()函數(shù); - 所有子 goroutine 由主 goroutine(或其他子 goroutine)創(chuàng)建;
- 主 goroutine 退出,整個程序立即結(jié)束,所有子 goroutine 都會被強制終止(不管是否執(zhí)行完)。
2. Channel (通道) - Go 并發(fā)的通信方式
(1)什么是 Channel
channel 是 Go 語言提供的原生同步通信機制,可以理解為「goroutine 之間的管道」。它的核心作用:讓多個 goroutine 之間安全的傳遞數(shù)據(jù)、實現(xiàn)同步 / 協(xié)作,完全契合 Go 的并發(fā)思想:通過通信來共享內(nèi)存。
(2)Channel 核心特點
- 通道是類型安全的:聲明通道時必須指定傳遞的數(shù)據(jù)類型,只能傳遞對應(yīng)類型的數(shù)據(jù);
- 通道是阻塞特性的(核心):
- 向一個通道發(fā)送數(shù)據(jù)
ch <- data時,如果通道滿了,發(fā)送方 goroutine 會被阻塞,直到有 goroutine 從通道取走數(shù)據(jù); - 從一個通道接收數(shù)據(jù)
data := <-ch時,如果通道空了,接收方 goroutine 會被阻塞,直到有 goroutine 向通道發(fā)送數(shù)據(jù); - 這種阻塞是 runtime 實現(xiàn)的,無額外開銷,是 goroutine 協(xié)作的核心原理;
- 向一個通道發(fā)送數(shù)據(jù)
- 通道可以手動關(guān)閉,關(guān)閉后無法再發(fā)送數(shù)據(jù),只能接收剩余數(shù)據(jù);
- 通道天然解決「數(shù)據(jù)競爭」:goroutine 之間不用共享變量,而是通過通道傳遞數(shù)據(jù),數(shù)據(jù)在 goroutine 之間 “流轉(zhuǎn)” 而非 “共享”。
(3)如何創(chuàng)建 Channel
語法:通道變量 := make(chan 數(shù)據(jù)類型, [緩沖區(qū)大小])
Channel 分為兩種:無緩沖通道 和 有緩沖通道,通過「緩沖區(qū)大小」區(qū)分,默認(rèn)是 0(無緩沖)。
① 無緩沖通道 (同步通道)
緩沖區(qū)大小為 0,聲明時可以省略第二個參數(shù):make(chan int)
- 特性:發(fā)送和接收必須同時就緒,否則會阻塞;
- 本質(zhì):實現(xiàn) goroutine 之間的嚴(yán)格同步,發(fā)送方發(fā)數(shù)據(jù)的瞬間,必須有接收方在取數(shù)據(jù),相當(dāng)于 “一手交錢、一手交貨”;
- 適用場景:goroutine 之間的強同步協(xié)作。
package main
import "fmt"
func sendData(ch chan int) {
fmt.Println("準(zhǔn)備發(fā)送數(shù)據(jù):100")
ch <- 100 // 發(fā)送數(shù)據(jù),無緩沖通道會阻塞,直到有g(shù)oroutine接收
fmt.Println("數(shù)據(jù)發(fā)送成功")
}
func main() {
// 創(chuàng)建無緩沖int類型通道
ch := make(chan int)
// 開啟goroutine發(fā)送數(shù)據(jù)
go sendData(ch)
// 主goroutine接收數(shù)據(jù),此時sendData的阻塞才會解除
data := <-ch
fmt.Printf("主協(xié)程接收數(shù)據(jù):%d\n", data)
}② 有緩沖通道 (異步通道)
緩沖區(qū)大小為 >0 的整數(shù),比如 make(chan int, 3) 表示能存放 3 個 int 數(shù)據(jù)的通道
- 特性:發(fā)送數(shù)據(jù)時,只要緩沖區(qū)沒滿,發(fā)送方不會阻塞;只有緩沖區(qū)滿了,發(fā)送方才會阻塞;接收數(shù)據(jù)時,只有緩沖區(qū)空了,接收方才會阻塞;
- 本質(zhì):實現(xiàn) goroutine 之間的異步通信,可以暫存數(shù)據(jù),不用嚴(yán)格同步;
- 適用場景:需要緩存數(shù)據(jù)、削峰填谷的并發(fā)場景。
package main
import "fmt"
func main() {
// 創(chuàng)建有緩沖通道,緩沖區(qū)大小為2
ch := make(chan string, 2)
// 發(fā)送數(shù)據(jù),緩沖區(qū)未滿,不會阻塞
ch <- "hello"
ch <- "goroutine"
fmt.Println("兩個數(shù)據(jù)發(fā)送成功,緩沖區(qū)已滿")
// 接收一個數(shù)據(jù),緩沖區(qū)剩余1個
fmt.Println("接收數(shù)據(jù):", <-ch)
// 再發(fā)送一個數(shù)據(jù),緩沖區(qū)未滿,不會阻塞
ch <- "channel"
// 遍歷接收所有數(shù)據(jù)
for i := 0; i < 2; i++ {
fmt.Println("接收數(shù)據(jù):", <-ch)
}
}執(zhí)行結(jié)果:
兩個數(shù)據(jù)發(fā)送成功,
緩沖區(qū)已滿
接收數(shù)據(jù): hello
接收數(shù)據(jù): goroutine
接收數(shù)據(jù): channel
(4)Channel 的關(guān)閉與遍歷
- 關(guān)閉通道:
close(ch),只能由發(fā)送方關(guān)閉,接收方關(guān)閉會 panic; - 判斷通道是否關(guān)閉:接收數(shù)據(jù)時可以用
data, ok := <-ch,ok為false表示通道已關(guān)閉且無數(shù)據(jù); - 遍歷通道:推薦用
for range ch,會自動遍歷通道的所有數(shù)據(jù),通道關(guān)閉后自動退出循環(huán)。
package main
import "fmt"
func sendData(ch chan int) {
for i := 1; i <= 3; i++ {
ch <- i // 發(fā)送數(shù)據(jù)
}
close(ch) // 發(fā)送完成,關(guān)閉通道
fmt.Println("通道已關(guān)閉")
}
func main() {
ch := make(chan int, 2)
go sendData(ch)
// for range 遍歷通道,自動處理關(guān)閉
for data := range ch {
fmt.Printf("接收數(shù)據(jù):%d\n", data)
}
fmt.Println("遍歷結(jié)束")
}三、Go 并發(fā)的補充核心知識點
1. WaitGroup - 等待一組 Goroutine 執(zhí)行完成
前面的例子中,我們用 time.Sleep 等待 goroutine 執(zhí)行完畢,這是極不推薦的寫法(睡眠時間無法精準(zhǔn)控制)。
Go 的 sync 包提供了 WaitGroup,專門用來等待一組 goroutine 全部執(zhí)行完成后,再繼續(xù)執(zhí)行主 goroutine,是生產(chǎn)環(huán)境中最常用的 goroutine 等待方式。
WaitGroup 核心方法
wg.Add(n):設(shè)置需要等待的 goroutine 數(shù)量,n是 goroutine 的個數(shù);wg.Done():每個 goroutine 執(zhí)行完成后調(diào)用,相當(dāng)于wg.Add(-1),表示完成一個;wg.Wait():主 goroutine 調(diào)用,阻塞直到所有 goroutine 都調(diào)用了wg.Done()(計數(shù)歸 0)。
示例
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup // 聲明WaitGroup
func task(name string) {
defer wg.Done() // 函數(shù)執(zhí)行完自動調(diào)用Done,防止遺漏
for i := 1; i <= 2; i++ {
fmt.Printf("任務(wù)[%s]執(zhí)行:%d\n", name, i)
}
}
func main() {
taskNum := 3
wg.Add(taskNum) // 需要等待3個goroutine
// 開啟3個goroutine
go task("goroutine-1")
go task("goroutine-2")
go task("goroutine-3")
wg.Wait() // 阻塞等待所有任務(wù)完成
fmt.Println("所有g(shù)oroutine執(zhí)行完畢,主協(xié)程退出")
}2. Go 并發(fā) vs 傳統(tǒng)多線程并發(fā)(核心優(yōu)勢總結(jié))
很多人會問:Java 也有線程、Python 也有協(xié)程,Go 的并發(fā)到底好在哪?這里做一個清晰的對比,也是 Go 的核心競爭力:

核心結(jié)論:Go 的并發(fā)不是 “錦上添花”,而是語言層面的降維打擊,它讓并發(fā)編程的門檻大幅降低,性能大幅提升,不用關(guān)心線程池、鎖機制,就能寫出高效、安全的并發(fā)代碼。
四、Go 并發(fā)的進階內(nèi)容(拓展,按需學(xué)習(xí))
這里補充兩個進階知識點,是 Go 并發(fā)的完整體系:
1. Mutex (互斥鎖) - 共享內(nèi)存的兜底方案
Go 的設(shè)計思想是「通信優(yōu)先于共享」,但極少數(shù)場景下,goroutine 之間必須共享變量(比如全局計數(shù)器),此時可以用 sync.Mutex 互斥鎖保證數(shù)據(jù)安全。
mutex.Lock():加鎖,同一時間只有一個 goroutine 能獲取鎖;mutex.Unlock():解鎖,釋放鎖資源;- 注意:盡量少用鎖,鎖會帶來競態(tài)、死鎖風(fēng)險,能用 channel 解決的問題,堅決不用鎖。
2. Select - 多路復(fù)用通道
select 是 Go 的關(guān)鍵字,專門用來同時監(jiān)聽多個 channel 的讀寫操作,實現(xiàn)多路并發(fā)控制,類似 Linux 的 IO 多路復(fù)用。
- 特性:select 會隨機選擇一個就緒的 case 執(zhí)行;如果所有 case 都阻塞,會阻塞到有 case 就緒;
- 適用場景:同時處理多個通道、設(shè)置通道超時、非阻塞讀寫通道等。
總結(jié)
- Go 并發(fā)的核心思想:不要通過共享內(nèi)存來通信,要通過通信來共享內(nèi)存;
- Go 并發(fā)的兩大基石:goroutine(輕量級協(xié)程,并發(fā)執(zhí)行體) + channel(通道,協(xié)程間通信方式);
- Goroutine:輕量(2KB 棧)、高效、百萬級創(chuàng)建,runtime 調(diào)度,主協(xié)程退出則程序結(jié)束;
- Channel:類型安全、阻塞特性,分無緩沖(同步)和有緩沖(異步),是 goroutine 的安全通信方式;
- 等待 goroutine 完成:用
sync.WaitGroup,禁止用 time.Sleep; - Go 并發(fā)的核心優(yōu)勢:相比傳統(tǒng)多線程,資源占用更少、調(diào)度更快、代碼更簡潔、數(shù)據(jù)更安全;
- 進階兜底:必須共享變量時用
sync.Mutex,多路通道控制用select。
到此這篇關(guān)于Go語言并發(fā)處理的使用詳解的文章就介紹到這了,更多相關(guān)Go語言 并發(fā)處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言循環(huán)遍歷含有中文的字符串的方法小結(jié)
這篇文章主要介紹了Go語言循環(huán)遍歷含有中文的字符串的幾種方法,文章通過代碼示例講解的非常詳細,具有一定的參考價值,感興趣的小伙伴跟著小編一起來看看吧2023-07-07
GoLang并發(fā)機制探究goroutine原理詳細講解
goroutine是Go語言提供的語言級別的輕量級線程,在我們需要使用并發(fā)時,我們只需要通過 go 關(guān)鍵字來開啟 goroutine 即可。這篇文章主要介紹了GoLang并發(fā)機制goroutine原理,感興趣的可以了解一下2022-12-12
Go中的關(guān)鍵字any interface是否會成為歷史
這篇文章主要為大家介紹了Go中的關(guān)鍵字any interface是否會成為歷史的講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07
go 迭代string數(shù)組操作 go for string[]
這篇文章主要介紹了go 迭代string數(shù)組操作 go for string[],具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

