網站首頁 編程語言 正文
前言
在業務代碼開發過程中,我們會有很大概率使用go語言的goroutine來開啟一個新的goroutine執行另外一段業務,或者開啟多個goroutine來并行執行多個業務邏輯。所以我為hade框架增加了兩個方法goroutine.SafeGo 和 goroutine.SafeGoAndWait。
封裝
SafeGo
SafeGo 這個函數,提供了一種goroutine安全的函數調用方式。主要適用于業務中需要進行開啟異步goroutine業務邏輯調用的場景。
// SafeGo 進行安全的goroutine調用 // 第一個參數是context接口,如果還實現了Container接口,且綁定了日志服務,則使用日志服務 // 第二個參數是匿名函數handler, 進行最終的業務邏輯 // SafeGo 函數并不會返回error,panic都會進入hade的日志服務 func SafeGo(ctx context.Context, handler func())
調用方式參照如下的單元測試用例:
func TestSafeGo(t *testing.T) { container := tests.InitBaseContainer() container.Bind(&log.HadeTestingLogProvider{}) ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) goroutine.SafeGo(ctx, func() { time.Sleep(1 * time.Second) return }) t.Log("safe go main start") time.Sleep(2 * time.Second) t.Log("safe go main end") goroutine.SafeGo(ctx, func() { time.Sleep(1 * time.Second) panic("safe go test panic") }) t.Log("safe go2 main start") time.Sleep(2 * time.Second) t.Log("safe go2 main end") }
SafeGoAndWait
SafeGoAndWait 這個函數,提供安全的多并發調用方式。該函數等待所有函數都結束后才返回。
// SafeGoAndWait 進行并發安全并行調用 // 第一個參數是context接口,如果還實現了Container接口,且綁定了日志服務,則使用日志服務 // 第二個參數是匿名函數handlers數組, 進行最終的業務邏輯 // 返回handlers中任何一個錯誤(如果handlers中有業務邏輯返回錯誤) func SafeGoAndWait(ctx context.Context, handlers ...func() error) error
調用方式參照如下的單元測試用例:
func TestSafeGoAndWait(t *testing.T) { container := tests.InitBaseContainer() container.Bind(&log.HadeTestingLogProvider{}) errStr := "safe go test error" t.Log("safe go and wait start", time.Now().String()) ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) err := goroutine.SafeGoAndWait(ctx, func() error { time.Sleep(1 * time.Second) return errors.New(errStr) }, func() error { time.Sleep(2 * time.Second) return nil }, func() error { time.Sleep(3 * time.Second) return nil }) t.Log("safe go and wait end", time.Now().String()) if err == nil { t.Error("err not be nil") } else if err.Error() != errStr { t.Error("err content not same") } // panic error err = goroutine.SafeGoAndWait(ctx, func() error { time.Sleep(1 * time.Second) return errors.New(errStr) }, func() error { time.Sleep(2 * time.Second) panic("test2") }, func() error { time.Sleep(3 * time.Second) return nil }) if err == nil { t.Error("err not be nil") } else if err.Error() != errStr { t.Error("err content not same") } }
實現說明
實現方面,有幾個難點記錄下。
首先是接口設計方面
可以看到handler函數在兩個接口中是不一樣的。在SafeGo接口中,handler定義為func()
?而在SafeGoAndWait中,定義為func() error
兩者的區別就在于SafeGo這個接口是沒有能力處理error的,因為它go出去一個goroutine就直接進行接下來的操作了。而SafeGoAndWait是必須等到所有的請求結束,所以它是有能力接收到error的。
所以SafeGo的handler沒有必要設置error返回值,而SafeGoAndWait是可以設置error的。
其次是日志兼容hade
如果出現了panic,如何將panic的日志打印出來。
整個框架我們并不希望有任何的全局變量,包括全局的Log,所以我這里做了一個兼容邏輯。
如果只是傳遞一個context,我們就使用官方的log包進行打印。
如果傳遞的是一個即實現了context,又實現了container接口的結構,我們就從container中獲取日志服務,來進行日志打印。這樣框架的所有日志就能統一在日志打印里面。
if logger != nil { logger.Error(ctx, "safe go handler panic", map[string]interface{}{ "stack": string(buf), "err": e, }) } else { log.Printf("panic\t%v\t%s", e, buf) }
由于我們修改了gin的context,讓它支持了我們的container容器結構,所以我們可以直接將gin.Context傳遞進來。具體使用起來就像這樣了:
// DemoGoroutine goroutine 的使用示例 func (api *DemoApi) DemoGoroutine(c *gin.Context) { logger := c.MustMakeLog() logger.Info(c, "request start", nil) // 初始化一個orm.DB gormService := c.MustMake(contract.ORMKey).(contract.ORMService) db, err := gormService.GetDB(orm.WithConfigPath("database.default")) if err != nil { logger.Error(c, err.Error(), nil) c.AbortWithError(50001, err) return } db.WithContext(c) err = goroutine.SafeGoAndWait(c, func() error { // 查詢一條數據 queryUser := &User{ID: 1} err = db.First(queryUser).Error logger.Info(c, "query user1", map[string]interface{}{ "err": err, "name": queryUser.Name, }) return err }, func() error { // 查詢一條數據 queryUser := &User{ID: 2} err = db.First(queryUser).Error logger.Info(c, "query user2", map[string]interface{}{ "err": err, "name": queryUser.Name, }) return err }) if err != nil { c.AbortWithError(50001, err) return } c.JSON(200, "ok") }
最后是打印panic的trace記錄
官方的panic其實打印的是所有goroutine的堆棧信息。但是這里我們希望打印的是出panic的那個堆棧信息。所以我們會使用
debug.Stack()
來打印出問題的goroutine的堆棧信息。
為了打印美觀,這里將換行符統一替換為\n?來進行展示。
具體的實現代碼可以參考github地址:https://github.com/gohade/hade/blob/main/framework/util/goroutine/goroutine.go
說明文檔:https://github.com/gohade/hade/blob/main/docs/guide/util.md
總結
為hade封裝了兩個SafeGo方法。特別是第二個SafeGoAndWait,在實際工作中確實是非常有用的。
原文鏈接:https://www.cnblogs.com/yjf512/p/15921756.html
相關推薦
- 2024-03-16 nginx 報 unknown directive “server“ 詭異問題處理
- 2022-11-02 Python函數命名空間,作用域LEGB及Global詳析_python
- 2022-11-17 使用flutter的showModalBottomSheet遇到的坑及解決_Android
- 2022-11-02 kotlin協程之coroutineScope函數使用詳解_Android
- 2022-04-01 關于python中if __name=‘__main__‘的理解
- 2022-06-06 typescript封裝屬性、public、private、protected、constructo
- 2022-09-10 Python自動打印被調用函數變量名及對應值?_python
- 2022-04-28 C++實現簡單班級成績管理系統_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支