golang goroutine

其實用go也用了一段時間,我是看視頻入門的,然后去買書,反正來來回回用了一年多的時間,很多點知道怎么用,相關(guān)知識也能答上來,但是始終有種感覺就是這樣會不會有問題,這個文檔就用來記錄我復盤golang的學習過程的。

當然基礎(chǔ)容器這些我是了解得夠多了,所以主要這個文集停留于用的層面,就是關(guān)于go的工程化,畢竟我們的代碼不單單是給我們自己看的,工程化代碼是很有必要的。所以文集的線索會從官方文檔到翻譯再加上一些輔助資料和實踐出真知的例子來說明相關(guān)關(guān)鍵字用法,當然在這個過程中也會發(fā)生很多錯誤,如果你看到了,或者你有什么疑問也可以在文章下面評論,我看到就會及時回復的。

goroutine

第一個文章不得不說一下這個明星關(guān)鍵字goroutine。這個小學生都會寫的關(guān)鍵字,我們到底如何用它

  • 1.盡可能自己做事而不是交給goroutine
  • 2.當你不知道goroutine什么時候會關(guān)閉的時候就不要使用goroutine
  • 3.管理起goroutine的聲明周期,開啟一個goroutine請問一下自己
    1:goroutine什么時候會關(guān)閉
    2:怎么去主動關(guān)閉goroutine

goroutine的易錯點

1.謹防goroutine泄露
When it comes to memory management, Go deals with many of the details for you. The Go compiler 
decides where values are located in memory using [escape analysis]
(https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html). The runtime 
tracks and manages heap allocations through the use of the [garbage collector]
(https://blog.golang.org/ismmkeynote). Though it’s not impossible to create [memory leaks]
(https://en.wikipedia.org/wiki/Memory_leak) in your applications, the chances are greatly reduced.

A common type of memory leak is leaking Goroutines. If you start a Goroutine that you expect to 
eventually terminate but it never does then it has leaked. It lives for the lifetime of the application and 
any memory allocated for the Goroutine can’t be released. This is part of the reasoning behind the 
advice “[Never start a goroutine without knowing how it will stop]
(https://dave.cheney.net/2016/12/22/never-start-a-goroutine-without-knowing-how-it-will-stop)”.

翻譯

并發(fā)編程允許開發(fā)人員使用多種執(zhí)行路徑來解決問題,并且經(jīng)常用于提高性能。并發(fā)并不意味著這些多
個路徑并行執(zhí)行。這意味著這些路徑是無序執(zhí)行,而不是順序執(zhí)行。從歷史上看,使用標準庫或第三方
開發(fā)人員提供的庫可以簡化此類編程。

在Go中,語言和運行時內(nèi)置了諸如Goroutine和通道之類的并發(fā)功能,以減少或消除對庫的需求。這產(chǎn)生
了一種幻想,即在Go中編寫并發(fā)程序很容易。在決定使用并發(fā)時,您必須謹慎,因為并發(fā)會帶來一些獨
特的副作用或陷阱(如果使用不正確)。如果不小心,這些陷阱會造成復雜性和令人討厭的錯誤。

案例:

31 // leak is a buggy function. It launches a goroutine that
32 // blocks receiving from a channel. Nothing will ever be
33 // sent on that channel and the channel is never closed so
34 // that goroutine will be blocked forever.
35 func leak() {
36     ch := make(chan int)
37 
38     go func() {
39         val := <-ch
40         fmt.Println("We received a value:", val)
41     }()
42 }

此時我們的goroutine的狀態(tài)稱為泄露狀態(tài),我們無法管控,無法退出,我們要嚴防這類代碼

2.控制goroutine退出

其實這里面有一定邏輯上的悖論,假設(shè)有個業(yè)務模型是這樣的:用戶注冊成功,發(fā)送通知短信,發(fā)送通知郵件,這種業(yè)務叫做旁路業(yè)務,也就是主路業(yè)務不需要再次管理這種旁路業(yè)務,延不延遲其實都沒太大所謂,用戶不在乎,對于我們自身來說的話也沒有太大的業(yè)務邏輯。那么就會出現(xiàn)這樣的情況:

func main(){
    
    // 用戶信息入庫
    

    // 發(fā)送短信
    go  sendMsg("注冊成功","1888888888")
    //todo other 
}

func sendMsg(msg string ,phoneNum int64)error{
  fmt.printf("給用戶%v,發(fā)送了消息:%v\n",msg,phoneNum)
}

其實這是可以完成的,只是說這種代碼非常危險,第一是對第三方平臺不友好,第二是goroutine無法管控。

  • 假設(shè)我們的goroutine突然第三方響應變得很慢,每個接口需要1min響應,那么在我們內(nèi)存中就有大量的goroutine,我們不能及時的取消這些goroutine,就導致很多有泄露的危險
  • 一般這種涉及到第三方的,對我們平臺都有一定的限流,很多的請求就會直接報錯誤碼,也就是會出現(xiàn)大量請求失敗

解決辦法:

  • 1.使用中間件消息隊列,但是我舉例的只是很小一個例子,其他不一定適用比如日志追蹤,請求日志這類就肯定沒必要丟到中間件又換個用戶去處理
  • 2.使用生產(chǎn)消費模型,啟動一個專門的goroutine去處理短信,然后使用一個channel傳遞,當我們通道塞滿了后可以選擇性的丟棄一半的buffer(當然丟棄指完全旁路,不想丟棄就加大消費者處理能力,比如多加一個goroutine)
3.管控goroutine執(zhí)行超時

其實這里我相信實際開發(fā)的人員都知道一個坑點,就是在select的時候正在進行一項任務,另外一個case進入信號其實是會被阻塞的,在研發(fā)初期我一直以為它會直接取消。那我們應該怎么來正確使用context的超時,或者channel的關(guān)閉廣播,實際context原理就是使用chan的廣播。

劃分業(yè)務:我們的業(yè)務其實細分起來是屬于算力型和網(wǎng)絡(luò)型

  • 算力型業(yè)務:最大應該也不超過100MS,算力型業(yè)務我們不應該執(zhí)行超過這個標準,否則就應該劃分函數(shù)了,而且算力型業(yè)務我們不能使用關(guān)閉它,只有等它完成才行
  • 網(wǎng)絡(luò)型業(yè)務:中間件之間的調(diào)用,rpc調(diào)用都是通過網(wǎng)絡(luò)模型來進行的,那么我們就可以比較容易的做到級聯(lián)取消,在rpc調(diào)用的首參數(shù)就是可以添加超時控制,但是我們操控中間件:redis、mysql 好像沒有context參數(shù)給我們使用,這是為什么呢?redis連接中實際上建立連接就有超時的參數(shù)設(shè)置,超時會報錯,mysql則第一是使用連接池,不能直接關(guān)閉連接,第二是mysql本身有超時控制,需要在mysql端設(shè)置。所以我們常見的xorm,gorm都沒有查詢超時的策略,這種的話只有優(yōu)化好我們的sql才行
4.野生goroutine 盡量少用

首先我們需要明確一點,在golang中沒有類似php的那種框架兜底,也就是不論報什么錯誤都有一個函數(shù)給你兜底,不會導致程序宕機,那么其實這就是很恐怖的,又尤其在recover 不能捕獲跨goroutine的錯誤,那么我們應該怎么來處理這個東西呢。
下面是我們采取的實例,簡單的這樣進行處理。

當然這種情況也要分的

  • 1.當go出去的是不可能報panic的,可以用我們封裝的也可以不用,但是我建議還是用
  • 2.go出去的東西實際上又非常多邏輯 ,這種需要使用我們封裝的
func main() {
    Go(func() {
        
    })
}

func Go(f func()){
    go func() {
        defer func() {
            if r:=recover();r!=nil{
                fmt.Println(r)
            }
        }()

        f()
    }()
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容