一文帶你更方便的控制 goroutine

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一篇我們講了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的併發工具包 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"core/syncx","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從整體分析來看,併發組件主要通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"channel + mutex","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制程序中協程之間溝通。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Do not communicate by sharing memory; instead, share memory by communicating.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不要通過共享內存來通信,而應通過通信來共享內存。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本篇來聊 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 對 Go 中 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"goroutine","attrs":{}}],"attrs":{}},{"type":"text","text":" 支持的併發組件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們回顧一下,go原生支持的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"goroutine","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制的工具有哪些?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":" 開啓一個協程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sync.WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 控制多個協程任務編排","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"sync.Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 協程喚醒或者是協程等待","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那可能會問 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 爲什麼還要拿出來講這些?回到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 的設計理念:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工具大於約定和文檔","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼就來看看,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 提供哪些工具?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"threading","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":" 已經很方便,但是有幾個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果協程異常退出,無法追蹤異常棧","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"某個異常請求觸發panic,應該做故障隔離,而不是整個進程退出,容易被攻擊","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看看 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"core/threading","attrs":{}}],"attrs":{}},{"type":"text","text":" 包提供了哪些額外選擇:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func GoSafe(fn func()) {\n go RunSafe(fn)\n}\n\nfunc RunSafe(fn func()) {\n defer rescue.Recover()\n fn()\n}\n\nfunc Recover(cleanups ...func()) {\n for _, cleanup := range cleanups {\n cleanup()\n }\n\n if p := recover(); p != nil {\n logx.ErrorStack(p)\n }\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"GoSafe","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"threading.GoSafe()","attrs":{}}],"attrs":{}},{"type":"text","text":" 就幫你解決了這個問題。開發者可以將自己在協程中需要完成邏輯,以閉包的方式傳入,由 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"GoSafe()","attrs":{}}],"attrs":{}},{"type":"text","text":" 內部 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go func()","attrs":{}}],"attrs":{}},{"type":"text","text":";","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當開發者的函數出現異常退出時,會在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Recover()","attrs":{}}],"attrs":{}},{"type":"text","text":" 中打印異常棧,以便讓開發者更快確定異常發生點和調用棧。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"NewWorkerGroup","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們再看第二個:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":"。日常開發,其實 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WaitGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 沒什麼好說的,你需要 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"N","attrs":{}}],"attrs":{}},{"type":"text","text":" 個協程協作 :","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Add(N)","attrs":{}}],"attrs":{}},{"type":"text","text":" ,等待全部協程完成任務:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Wait()","attrs":{}}],"attrs":{}},{"type":"text","text":",同時完成一個任務需要手動 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wg.Done()","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看的出來,在任務開始 -> 結束 -> 等待,整個過程需要開發者關注任務的狀態然後手動修改狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"NewWorkerGroup","attrs":{}}],"attrs":{}},{"type":"text","text":" 就幫開發者減輕了負擔,開發者只需要關注:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"任務邏輯【函數】","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"任務數【","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"workers","attrs":{}}],"attrs":{}},{"type":"text","text":"】","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後啓動 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"WorkerGroup.Start()","attrs":{}}],"attrs":{}},{"type":"text","text":",對應任務數就會啓動:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (wg WorkerGroup) Start() {\n // 包裝了sync.WaitGroup\n group := NewRoutineGroup()\n for i := 0; i < wg.workers; i++ {\n // 內部維護了 wg.Add(1) wg.Done()\n // 同時也是 goroutine 安全模式下進行的\n group.RunSafe(wg.job)\n }\n group.Wait()\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"worker","attrs":{}}],"attrs":{}},{"type":"text","text":" 的狀態會自動管理,可以用來固定數量的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"worker","attrs":{}}],"attrs":{}},{"type":"text","text":" 來處理消息隊列的任務,用法如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func main() {\n group := NewWorkerGroup(func() {\n // process tasks\n }, runtime.NumCPU())\n group.Start()\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Pool","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 不是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Pool","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"sync.Pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 有個不方便的地方是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"它池化的對象可能會被垃圾回收掉","attrs":{}},{"type":"text","text":",這個就讓開發者疑惑了,不知道自己創建並存入的對象什麼時候就沒了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的對象會根據使用時間做懶銷燬;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 做對象消費和生產的通知以及阻塞;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"開發者可以自定義自己的生產函數,銷燬函數;","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那我來看看生產對象,和消費對象在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"pool","attrs":{}}],"attrs":{}},{"type":"text","text":" 中時怎麼實現的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (p *Pool) Get() interface{} {\n // 調用 cond.Wait 時必須要持有c.L的鎖\n p.lock.Lock()\n defer p.lock.Unlock()\n\n for {\n // 1. pool中對象池是一個用鏈表連接的nodelist\n if p.head != nil {\n head := p.head\n p.head = head.next\n // 1.1 如果當前節點:當前時間 >= 上次使用時間+對象最大存活時間\n if p.maxAge > 0 && head.lastUsed+p.maxAge < timex.Now() {\n p.created--\n // 說明當前節點已經過期了 -> 銷燬節點對應的對象,然後繼續尋找下一個節點\n // 【⚠️:不是銷燬節點,而是銷燬節點對應的對象】\n p.destroy(head.item)\n continue\n } else {\n return head.item\n }\n }\n // 2. 對象池是懶加載的,get的時候纔去創建對象鏈表\n if p.created < p.limit {\n p.created++\n // 由開發者自己傳入:生產函數\n return p.create()\n }\n \n p.cond.Wait()\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func (p *Pool) Put(x interface{}) {\n if x == nil {\n return\n }\n // 互斥訪問 pool 中nodelist\n p.lock.Lock()\n defer p.lock.Unlock()\n\n p.head = &node{\n item: x,\n next: p.head,\n lastUsed: timex.Now(),\n }\n // 放入head,通知其他正在get的協程【極爲關鍵】\n p.cond.Signal()\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 對 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 的使用。可以類比 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"生產者-消費者模型","attrs":{}},{"type":"text","text":",只是在這裏沒有使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"channel","attrs":{}}],"attrs":{}},{"type":"text","text":" 做通信,而是用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Cond","attrs":{}}],"attrs":{}},{"type":"text","text":" 。這裏有幾個特性:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cond和一個Locker關聯,可以利用這個Locker對相關的依賴條件更改提供保護。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Cond可以同時支持 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Signal","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Broadcast","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,而 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Channel","attrs":{}}],"attrs":{}},{"type":"text","text":" 只能同時支持其中一種。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"工具大於約定和文檔","attrs":{}},{"type":"text","text":",一直是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 設計主旨之一;也同時將平時業務沉澱到組件中,這纔是框架和組件的意義。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"go-zero","attrs":{}}],"attrs":{}},{"type":"text","text":" 更多的設計和實現文章,可以持續關注我們。歡迎大家去關注和使用。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"項目地址","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/tal-tech/go-zero","title":"","type":null},"content":[{"type":"text","text":"https://github.com/tal-tech/go-zero","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎使用 go-zero 並 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"star","attrs":{}},{"type":"text","text":" 支持我們!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"微信交流羣","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注『微服務實踐』公衆號並回復 進羣 獲取社區羣二維碼。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章