一文帶你更方便的控制 goroutine
從整體分析來看,併發元件主要透過channel+mutex控制程式中協程之間溝通。
Do not communicate by sharing memory;instead,share memory by communicating.
不要透過共享記憶體來通訊,而應透過通訊來共享記憶體。
本篇來聊go-zero對Go中goroutine支援的併發元件。
我們回顧一下,go原生支援的goroutine控制的工具有哪些?
go func()開啟一個協程
sync.WaitGroup控制多個協程任務編排
sync.Cond協程喚醒或者是協程等待
那可能會問go-zero為什麼還要拿出來講這些?回到go-zero的設計理念:工具大於約定和文件。
那麼就來看看,go-zero提供哪些工具?
threading
雖然go func()已經很方便,但是有幾個問題:
如果協程異常退出,無法追蹤異常棧
某個異常請求觸發panic,應該做故障隔離,而不是整個程式退出,容易被攻擊
我們看看core/threading包提供了哪些額外選擇:
func GoSafe(fn func()){
go RunSafe(fn)
}
func RunSafe(fn func()){
defer rescue.Recover()
fn()
}
func Recover(cleanups...func()){
for _,cleanup:=range cleanups{
cleanup()
}
if p:=recover();p!=nil{
logx.ErrorStack(p)
}
}
GoSafe
threading.GoSafe()就幫你解決了這個問題。開發者可以將自己在協程中需要完成邏輯,以閉包的方式傳入,由GoSafe()內部go func();
當開發者的函式出現異常退出時,會在Recover()中列印異常棧,以便讓開發者更快確定異常發生點和呼叫棧。
NewWorkerGroup
我們再看第二個:WaitGroup。日常開發,其實WaitGroup沒什麼好說的,你需要N個協程協作:wg.Add(N),等待全部協程完成任務:wg.Wait(),同時完成一個任務需要手動wg.Done()。
可以看的出來,在任務開始->結束->等待,整個過程需要開發者關注任務的狀態然後手動修改狀態。
NewWorkerGroup就幫開發者減輕了負擔,開發者只需要關注:
任務邏輯【函式】
任務數【workers】
然後啟動WorkerGroup.Start(),對應任務數就會啟動:
func(wg WorkerGroup)Start(){
//包裝了sync.WaitGroup
group:=NewRoutineGroup()
for i:=0;i<wg.workers;i++{
//內部維護了wg.Add(1)wg.Done()
//同時也是goroutine安全模式下進行的
group.RunSafe(wg.job)
}
group.Wait()
}
worker的狀態會自動管理,可以用來固定數量的worker來處理訊息佇列的任務,用法如下:
func main(){
group:=NewWorkerGroup(func(){
//process tasks
},runtime.NumCPU())
group.Start()
}
Pool
這裡的Pool不是sync.Pool。sync.Pool有個不方便的地方是它池化的物件可能會被垃圾回收掉,這個就讓開發者疑惑了,不知道自己建立並存入的物件什麼時候就沒了。
go-zero中的pool:
pool中的物件會根據使用時間做懶銷燬;
使用cond做物件消費和生產的通知以及阻塞;
開發者可以自定義自己的生產函式,銷燬函式;
那我來看看生產物件,和消費物件在pool中時怎麼實現的:
func(p*Pool)Get()interface{}{
//呼叫cond.Wait時必須要持有c.L的鎖
p.lock.Lock()
defer p.lock.Unlock()
for{
//1.pool中物件池是一個用連結串列連線的nodelist
if p.head!=nil{
head:=p.head
p.head=head.next
//1.1如果當前節點:當前時間>=上次使用時間+物件最大存活時間
if p.maxAge>0&&head.lastUsed+p.maxAge<timex.Now(){
p.created--
//說明當前節點已經過期了->銷燬節點對應的物件,然後繼續尋找下一個節點
//【⚠️:不是銷燬節點,而是銷燬節點對應的物件】
p.destroy(head.item)
continue
}else{
return head.item
}
}
//2.物件池是懶載入的,get的時候才去建立物件連結串列
if p.created<p.limit{
p.created++
//由開發者自己傳入:生產函式
return p.create()
}
p.cond.Wait()
}
}
func(p*Pool)Put(x interface{}){
if x==nil{
return
}
//互斥訪問pool中nodelist
p.lock.Lock()
defer p.lock.Unlock()
p.head=&node{
item:x,
next:p.head,
lastUsed:timex.Now(),
}
//放入head,通知其他正在get的協程【極為關鍵】
p.cond.Signal()
}
上述就是go-zero對Cond的使用。可以類比生產者-消費者模型,只是在這裡沒有使用channel做通訊,而是用Cond。這裡有幾個特性:
Cond和一個Locker關聯,可以利用這個Locker對相關的依賴條件更改提供保護。
Cond可以同時支援Signal和Broadcast方法,而Channel只能同時支援其中一種。
總結
工具大於約定和文件,一直是go-zero設計主旨之一;也同時將平時業務沉澱到元件中,這才是框架和元件的意義。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69995861/viewspace-2768661/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- macOS 新功能:【控制中心】讓你的 Mac 系統更方便!Mac
- iOS 如何更方便的給控制元件新增Action?iOS控制元件
- 她,可以讓你更方便的使用React.forwardRefReactForward
- goroutine併發控制Go
- 智慧家居讓生活更方便但也帶來新的安全問題
- 一文帶你快速掌握AQSAQS
- 一文帶你搞懂RPCRPC
- 一文帶你認識DockerDocker
- 一文帶你入門 GolangGolang
- 攜帶更方便功能全 iPone與Apple Watch球形尿袋APP
- go中控制goroutine數量Go
- 在Mac上觀看畫中畫影片,讓你的使用更方便Mac
- 一文帶你搞懂 CDN 的技術原理
- 一文帶你瞭解 JS Module 的始末JS
- 一文帶你入門LinuxLinux
- 一文帶你看清 HTTP 所有概念HTTP
- 一文帶你入門TransformerORM
- 一文帶你入門圖機器學習機器學習
- 一文帶你瞭解Java的命名規範!Java
- 一文帶你理解深度學習的侷限性深度學習
- 一文帶你瞭解nginx基礎Nginx
- 一文帶你學習SpringBootSpring Boot
- 一文帶你看懂Spring事務!Spring
- 一文帶你瞭解HDFS技術
- 一文帶你看透IP歸屬地
- 【基礎篇】一文帶你掌握 RedisRedis
- 一文帶你弄懂 Maven 拉包原理Maven
- 一文帶你吃透作業系統作業系統
- 一文帶你理解透MyBatis原始碼MyBatis原始碼
- Go程式設計技巧–Goroutine的優雅控制Go程式設計
- 一文帶你過完Spark RDD的基礎概念Spark
- 一文帶你瞭解 JVM 的垃圾回收機制JVM
- 一文帶你瞭解python中的多型Python多型
- [譯] 一文帶你學會全部Flutter的ProviderFlutterIDE
- 一文帶你搞清 ChatGPT 與 Azure OpenAI 的區別ChatGPTOpenAI
- Android :一文帶你真正入門神祕的RxjavaAndroidRxJava
- Java 8:一文帶你掌握 Lambda 表示式Java
- 一文帶你徹底理解 JavaScript 原型物件JavaScript原型物件