Go 通道引用與close操作的實(shí)現(xiàn)
在 Go 開發(fā)中,通道(chan)的使用頻率極高,但它的引用特性和 close 操作的作用范圍,往往是新手容易踩坑的點(diǎn)。比如 “通道賦值后關(guān)閉原變量,新變量會受影響嗎?”“會不會導(dǎo)致內(nèi)存泄露?”“后續(xù)發(fā)送數(shù)據(jù)會不會 panic?”—— 這篇文章就用通俗的語言 + 結(jié)論 + 代碼驗(yàn)證,把這些問題講透。
一、核心結(jié)論(先給答案,不繞彎)
destChan不是指針,是「通道引用」:Go 中通道是引用類型(類似 slice、map),變量存儲的是指向底層數(shù)據(jù)結(jié)構(gòu)的引用,而非結(jié)構(gòu)本身。- 關(guān)閉
source.TaskChan后,destChan會受影響,但不是 “被關(guān)閉”:close 操作作用于底層通道,所有引用這個(gè)通道的變量(包括destChan)都會感知到 “通道已關(guān)閉”。 - 不會因
destChan導(dǎo)致內(nèi)存泄露:只要所有引用(source.TaskChan和destChan)都不再被使用,底層通道會被 GC 回收;真正需要警惕的是 “關(guān)閉通道后的發(fā)送 panic”。
二、逐點(diǎn)拆解:把原理講明白
1. 通道是 “引用類型”,不是指針但行為類似
Go 中的引用類型(chan/slice/map/func/interface)有個(gè)共性:變量存儲的是「指向底層對象的地址」,賦值操作只會復(fù)制這個(gè)地址,不會復(fù)制底層對象。
舉個(gè)實(shí)際場景的例子:
// 假設(shè) source 是一個(gè)自定義結(jié)構(gòu)體,TaskChan 是已初始化的 chan string
type Resource struct {
TaskChan chan string // 通道字段
}
var source = Resource{
TaskChan: make(chan string, 5), // 初始化帶緩沖通道
}
// 賦值操作:將 source.TaskChan 賦值給 destChan
destChan := source.TaskChan
執(zhí)行后,destChan 和 source.TaskChan 持有同一個(gè)底層通道的引用 —— 就像兩個(gè)遙控器控制同一個(gè)電視,操作任何一個(gè),影響的都是同一個(gè) “底層設(shè)備”。
這里要注意:destChan 不是 *chan string(通道指針),而是 chan string(通道引用類型),語法上不需要解引用(*)就能直接使用,比指針更簡潔。
2. close 操作作用于 “底層通道”,所有引用都會感知
當(dāng)我們執(zhí)行 close(source.TaskChan) 時(shí),要明確一個(gè)關(guān)鍵:關(guān)閉的是底層的通道對象,不是 source.TaskChan 這個(gè)變量本身。
因?yàn)?nbsp;destChan 和 source.TaskChan 指向同一個(gè)底層通道,所以 destChan 會變成 “指向已關(guān)閉通道的引用”—— 此時(shí)會有兩個(gè)核心影響:
- 往
destChan發(fā)送數(shù)據(jù):直接 panic(錯(cuò)誤信息:send on closed channel); - 從
destChan接收數(shù)據(jù):會立即返回通道元素的零值 +ok=false(表示通道已關(guān)閉且無數(shù)據(jù))。
可以用一個(gè)通俗的比喻理解:兩個(gè)指針指向同一個(gè)文件,關(guān)閉文件后,兩個(gè)指針都無法再寫入文件,但指針變量本身還存在(不是 nil),只是失去了有效操作的能力。
3. 內(nèi)存泄露風(fēng)險(xiǎn):幾乎不存在,無需過度擔(dān)心
內(nèi)存泄露的核心是 “底層對象被無用的引用持有,無法被 GC 回收”,但在這個(gè)場景中,完全不需要擔(dān)心:
destChan通常是局部變量(比如在函數(shù)或回調(diào)中定義),函數(shù)執(zhí)行完畢后,變量會被銷毀,引用自然釋放;source.TaskChan是結(jié)構(gòu)體字段,當(dāng)source結(jié)構(gòu)體被銷毀(比如任務(wù)執(zhí)行結(jié)束后),這個(gè)引用也會消失;- 只要所有引用都釋放,無論底層通道是否關(guān)閉,都會被 GC 回收,不會造成內(nèi)存泄露。
唯一可能的泄露場景:如果 source 是全局變量(長期存在),且通道被關(guān)閉后,source.TaskChan 仍被持有,但這是 source 的生命周期管理問題,和 destChan 無關(guān)。
三、代碼驗(yàn)證:直觀感受引用與 close 的影響
光說不練假把式,用一段簡單的代碼驗(yàn)證上面的結(jié)論,跑起來就能直觀看到效果:
package main
import "fmt"
func main() {
// 1. 初始化一個(gè)通道(底層通道對象在堆上分配)
sourceChan := make(chan string, 1)
fmt.Printf("sourceChan 變量本身地址(棧上):%p\n", &sourceChan)
fmt.Printf("sourceChan 引用的底層通道地址(堆上):%p\n", sourceChan)
// 2. 賦值給 destChan:復(fù)制引用
destChan := sourceChan
fmt.Printf("destChan 變量本身地址(棧上):%p\n", &destChan)
fmt.Printf("destChan 引用的底層通道地址(堆上):%p\n", destChan)
// 3. 關(guān)閉 sourceChan(實(shí)際關(guān)閉的是底層通道)
close(sourceChan)
// 4. destChan 感知到底層通道已關(guān)閉
data, ok := <-destChan
fmt.Printf("從 destChan 接收數(shù)據(jù):data=%q, ok=%v(ok=false 表示通道已關(guān)閉)\n", data, ok)
// 5. 往 destChan 發(fā)送數(shù)據(jù)會直接 panic(注釋掉可避免運(yùn)行報(bào)錯(cuò))
// destChan <- "test" // 執(zhí)行后報(bào)錯(cuò):panic: send on closed channel
}
運(yùn)行結(jié)果:
sourceChan 變量本身地址(棧上):0xc0000a6020
sourceChan 引用的底層通道地址(堆上):0xc0000b4000
destChan 變量本身地址(棧上):0xc0000a6040
destChan 引用的底層通道地址(堆上):0xc0000b4000
從 destChan 接收數(shù)據(jù):data="", ok=false(ok=false 表示通道已關(guān)閉)
結(jié)論驗(yàn)證:
sourceChan和destChan是不同的變量(棧地址不同),但引用同一個(gè)底層通道(堆地址相同);- 關(guān)閉
sourceChan后,destChan能直接感知到通道關(guān)閉; - 關(guān)閉后的通道發(fā)送數(shù)據(jù)會 panic,這是核心風(fēng)險(xiǎn)點(diǎn)。
到此這篇關(guān)于Go 通道引用與close操作的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go 通道引用與close操作內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go?WaitGroup及Cond底層實(shí)現(xiàn)原理
這篇文章主要為大家介紹了Go?WaitGroup及Cond底層實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
gRPC的發(fā)布訂閱模式及REST接口和超時(shí)控制
這篇文章主要為大家介紹了gRPC的發(fā)布訂閱模式及REST接口和超時(shí)控制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
golang 檢查網(wǎng)絡(luò)狀態(tài)是否正常的方法
今天小編就為大家分享一篇golang 檢查網(wǎng)絡(luò)狀態(tài)是否正常的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07
Golang實(shí)現(xiàn)定時(shí)任務(wù)的幾種方法小結(jié)
在 Golang 開發(fā)中,定時(shí)任務(wù)是常見的需求,本文將介紹幾種在 Golang 中實(shí)現(xiàn)定時(shí)任務(wù)的方法,包括 time 包的定時(shí)器、ticker,以及第三方庫 cron,并通過示例代碼展示它們的使用方式,需要的朋友可以參考下2024-01-01
使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法
go語言web框架挺多的,本文就介紹了一下如何使用gin框架搭建簡易服務(wù)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
golang如何用type-switch判斷interface變量的實(shí)際存儲類型
這篇文章主要介紹了golang如何用type-switch判斷interface變量的實(shí)際存儲類型,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
golang常用庫之pkg/errors包第三方錯(cuò)誤處理包案例詳解
這篇文章主要介紹了golang常用庫之pkg/errors包第三方錯(cuò)誤處理包,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
golang生成指定位數(shù)的隨機(jī)數(shù)的方法
這篇文章主要介紹了golang生成指定位數(shù)的隨機(jī)數(shù)的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

