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