協(xié)程泄漏
協(xié)程和內(nèi)存一樣,是系統(tǒng)的資源。對(duì)于內(nèi)存,有自動(dòng)垃圾回收。但是對(duì)于協(xié)程,沒有相應(yīng)的回收機(jī)制。會(huì)不會(huì)若干年后,協(xié)程普及了,協(xié)程泄漏和內(nèi)存泄漏一樣成為 程序員永遠(yuǎn)的痛呢?一般而言,協(xié)程執(zhí)行結(jié)束后就會(huì)銷毀。協(xié)程也會(huì)占用內(nèi)存,如果發(fā)生協(xié)程泄漏,影響和內(nèi)存泄漏一樣嚴(yán)重。輕則拖慢程序,重則壓垮機(jī)器。
C和C++都是沒有自動(dòng)內(nèi)存回收的程序設(shè)計(jì)語言,但只要有良好的編程習(xí)慣,就能解決規(guī)避問題。對(duì)于協(xié)程是一樣的,只要有好習(xí)慣就可以了。
只有兩種情況會(huì)導(dǎo)致協(xié)程無法結(jié)束。一種情況是協(xié)程想從一個(gè)通道讀數(shù)據(jù),但無人往這個(gè)通道寫入數(shù)據(jù),或許這個(gè)通道已經(jīng)被遺忘了。還有一種情況是程想往一個(gè)通道寫數(shù)據(jù),可是由于無人監(jiān)聽這個(gè)通道,該協(xié)程將永遠(yuǎn)無法向下執(zhí)行。下面分別討論如何避免這兩種情況。
對(duì)于協(xié)程想從一個(gè)通道讀數(shù)據(jù),但無人往這個(gè)通道寫入數(shù)據(jù)這種情況。解決的辦法很簡(jiǎn)單,加入超時(shí)機(jī)制。對(duì)于有不確定會(huì)不會(huì)返回的情況,必須加入超時(shí),避免出 現(xiàn)永久等待。另外不一定要使用定時(shí)器才能終止協(xié)程。也可以對(duì)外暴露一個(gè)退出提醒通道。任何其他協(xié)程都可以通過該通道來提醒這個(gè)協(xié)程終止。
對(duì)于協(xié)程想往一個(gè)通道寫數(shù)據(jù),但通道阻塞無法寫入這種情況。解決的辦法也很簡(jiǎn)單,就是給通道加緩沖。但前提是這個(gè)通道只會(huì)接收到固定數(shù)目的寫入。比方說, 已知一個(gè)通道最多只會(huì)接收N次數(shù)據(jù),那么就將這個(gè)通道的緩沖設(shè)置為N。那么該通道將永遠(yuǎn)不會(huì)堵塞,協(xié)程自然也不會(huì)泄漏。也可以將其緩沖設(shè)置為無限,不過這 樣就要承擔(dān)內(nèi)存泄漏的風(fēng)險(xiǎn)了。等協(xié)程執(zhí)行完畢后,這部分通道內(nèi)存將會(huì)失去引用,會(huì)被自動(dòng)垃圾回收掉。
funcnever_leak(ch chan int) {
//初始化timeout,緩沖為1
timeout := make(chan bool, 1)
//啟動(dòng)timeout協(xié)程,由于緩存為1,不可能泄露
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
//監(jiān)聽通道,由于設(shè)有超時(shí),不可能泄露
select {
case <-ch:
// a read from ch hasoccurred
case <-timeout:
// the read from ch has timedout
}
}
上面是個(gè)避免泄漏例子。使用超時(shí)避免讀堵塞,使用緩沖避免寫堵塞。
和內(nèi)存里面的對(duì)象一樣,對(duì)于長期存在的協(xié)程,我們不用擔(dān)心泄漏問題。一是長期存在,二是數(shù)量較少。要警惕的只有那些被臨時(shí)創(chuàng)建的協(xié)程,這些協(xié)程數(shù)量大且生 命周期短,往往是在循環(huán)中創(chuàng)建的,要應(yīng)用前面提到的辦法,避免泄漏發(fā)生。協(xié)程也是把雙刃劍,如果出問題,不但沒能提高程序性能,反而會(huì)讓程序崩潰。但就像 內(nèi)存一樣,同樣有泄漏的風(fēng)險(xiǎn),但越用越溜了。