新聞中心
大家好,我是煎魚。

創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站制作、成都網(wǎng)站建設(shè)與策劃設(shè)計(jì),義縣網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)10年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:義縣等地區(qū)。義縣做網(wǎng)站價(jià)格咨詢:028-86922220
在 Go 中有一個(gè)很經(jīng)典的設(shè)計(jì):context,這是許多同學(xué)初學(xué)時(shí)必學(xué)的標(biāo)準(zhǔn)庫(kù)。涉及到上下文傳遞、超時(shí)控制等必要項(xiàng)。
甚至在函數(shù)體中的第一個(gè)參數(shù)大多是傳 context。寫第三方庫(kù)也必須兼容 context 設(shè)置,否則會(huì)經(jīng)常有人提需求讓你支持。
我覺(jué)得這次的新特性更新雖不復(fù)雜,但作用挺大。建議大家學(xué)習(xí)!
Context Demo
以下是一個(gè)快速 Demo:
package main
import (
"context"
"fmt"
"time"
)
const shortDuration = 1 * time.Millisecond
func main() {
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
}運(yùn)行結(jié)果:
context deadline exceeded一切都看起來(lái)沒(méi)什么問(wèn)題。
麻煩點(diǎn)
但在實(shí)際寫業(yè)務(wù)代碼和排查問(wèn)題時(shí),你就會(huì)發(fā)現(xiàn)一個(gè)麻煩的事。在出現(xiàn)上下文超時(shí)或到達(dá)所設(shè)置的截止時(shí)間時(shí),ctx.Err 方法可以獲得 context deadline exceeded 的錯(cuò)誤信息。
但這是遠(yuǎn)遠(yuǎn)不夠的,你只知道是因?yàn)檎T發(fā)了超時(shí)。但不知道是哪里導(dǎo)致的,還得再去根據(jù)訪問(wèn)的邏輯,再走一遍腦洞,再進(jìn)行排查。又或是根據(jù)代碼堆棧,再去設(shè)想,最后復(fù)現(xiàn)成功。
又或是查不到。因?yàn)檫@種一般是偶現(xiàn),很有可能就留給下一代的繼承者了~
又更有業(yè)務(wù)訴求,希望在出現(xiàn)上下文的異常場(chǎng)景時(shí),可以及時(shí)執(zhí)行回調(diào)方法。然而這沒(méi)有太便捷的實(shí)現(xiàn)方式。
Go1.21 增強(qiáng) Context
增加 WithXXXCause
在即將發(fā)布的 Go1.21,針對(duì) Context 的錯(cuò)誤處理終于有了一點(diǎn)點(diǎn)的增強(qiáng),來(lái)填補(bǔ)這個(gè)地方的信息,允許添加自定義的錯(cuò)誤類型和信息。
新增的 Context API 如下:
// WithDeadlineCause behaves like WithDeadline but also sets the cause of the
// returned Context when the deadline is exceeded. The returned CancelFunc does
// not set the cause.
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
// WithTimeoutCause behaves like WithTimeout but also sets the cause of the
// returned Context when the timout expires. The returned CancelFunc does
// not set the cause.
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)與原先的 WithDeadline 和 WithTimeout 作用基本一致,唯一區(qū)別就是在形參上增加了 cause error,允許傳入錯(cuò)誤類型。
WithTimeoutCause
WithTimeoutCause 的使用示例:
tooSlow := fmt.Errorf("too slow!")
ctx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Second, tooSlow)
time.Sleep(2*time.Second)
cancel()像上述程序,執(zhí)行 ctx.Err 方法時(shí)得到的結(jié)果是:context.DeadlineExceeded,這是既有的。
此時(shí),我們?cè)俳Y(jié)合在 Go1.20 版本加入的 context.Cause 方法:
func Cause(c Context) error就能得到對(duì)應(yīng)的錯(cuò)誤信息,上述的結(jié)果對(duì)應(yīng)的是 tooSlow 變量。
WithCancelCause
WithCancelCause 的使用示例,計(jì)時(shí)器先觸發(fā):
finishedEarly := fmt.Errorf("finished early")
tooSlow := fmt.Errorf("too slow!")
ctx, cancel := context.WithCancelCause(context.Background())
ctx, _ = context.WithTimeoutCause(ctx, 1*time.Second, tooSlow)
time.Sleep(2*time.Second) // timer fires, setting the cause
cancel(finishedEarly) // no effect as ctx has already been canceled對(duì)應(yīng)的程序結(jié)果:
- ctx.Err():context.DeadlineExceeded 類型。
- context.Cause(ctx):tooSlow 類型。
先發(fā)生上下文取消的使用示例:
finishedEarly := fmt.Errorf("finished early")
tooSlow := fmt.Errorf("too slow!")
ctx, cancel := context.WithCancelCause(context.Background())
ctx, _ = context.WithTimeoutCause(ctx, 1*time.Second, tooSlow)
time.Sleep(500*time.Millisecond) // timer hasn't expired yet
cancel(finishedEarly) // cancels the timer and sets ctx.Err()對(duì)應(yīng)的程序結(jié)果:
- ctx.Err():context.Canceled 類型。
- context.Cause(ctx):finishedEarly 類型。
增加 AfterFunc
同樣的,在 Go1.21 也對(duì) Context(上下文)被取消的動(dòng)作后增加了一些增強(qiáng)。平時(shí)當(dāng)上下文被取消時(shí),我們只能通過(guò)啟動(dòng) Goroutine 來(lái)監(jiān)視取消行為并做一系列操作。
但這未免繁瑣且增大了我們的編碼和運(yùn)行成本,因?yàn)槊看翁幚矶家?goroutine+select+channel 來(lái)一套組合拳,才能真正到寫自己業(yè)務(wù)代碼的地方。
為此新版本增加了注冊(cè)函數(shù)的功能,將會(huì)在上下文被取消時(shí)調(diào)用。函數(shù)簽名如下:
func AfterFunc(ctx Context, f func()) (stop func() bool)在函數(shù)作用上,該函數(shù)會(huì)在 ctx 完成(取消或超時(shí))后調(diào)用所傳入的函數(shù) f。
在運(yùn)行機(jī)制上,它會(huì)自己在 goroutine 中調(diào)用 f。需要注意的是,即使 ctx 已經(jīng)完成,調(diào)用 AfterFunc 也不會(huì)等待 f 返回。
這也是可以套娃的,在 AfterFunc 里再套 AfterFunc。這里用不好也很容易 goroutine 泄露。
基于這個(gè)新函數(shù),可以看看以下兩個(gè)例子作為使用場(chǎng)景。
1、多 Context 合并取消的例子:
func WithFirstCancel(ctx1, ctx2 context.Context) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(ctx1)
stopf := context.AfterFunc(ctx2, func() {
cancel()
})
return ctx, func() {
cancel()
stopf()
}
}2、在取消上下文時(shí)停止等待 sync.Cond:
func Wait(ctx context.Context, cond *sync.Cond) error {
stopf := context.AfterFunc(ctx, cond.Broadcast)
defer stopf()
cond.Wait()
return ctx.Err()
}基本滿足了各種上下文的復(fù)雜訴求了。
總結(jié)
Context 一直是大家使用的最頻繁的標(biāo)準(zhǔn)庫(kù)之一,他聯(lián)通了整個(gè) Go 里的工程體系。這次在 Go1.21 對(duì) Context 增加了 WithXXXCause 相關(guān)函數(shù)的錯(cuò)誤類型支持。對(duì)于我們?cè)?Go 工程實(shí)踐中的排查和定位,能夠有一些不錯(cuò)的助力。
另外 AfterFunc 函數(shù)的增加,看起來(lái)是個(gè)簡(jiǎn)單的功能。但是可以解決以往的一些合并取消上下文和串聯(lián)處理的復(fù)雜場(chǎng)景,是一個(gè)不錯(cuò)的擴(kuò)展功能。
苛刻些,美中不足的就是,Go 都已經(jīng)發(fā)布 10+ 年了,加的還是有些太晚了。同時(shí)針對(duì) Context 也需要有更體系的排查和定位側(cè)的補(bǔ)全了。
分享名稱:Go1.21速覽:Context可以設(shè)置取消原因和回調(diào)函數(shù)了,等的可太久了!
瀏覽地址:http://m.5511xx.com/article/dhoesie.html


咨詢
建站咨詢
