Go 通常可以使用以下 3 种方式实现超时:
- Timeout Context 实现超时
- Cancel Context 实现超时
- 手动操作 channel 实现超时
推荐使用 Cancel Context 的方式实现超时功能,因为区分是正常结束还是超时结束比较容易。
Timeout Context 实现超时
TimeoutContext 的特点:
- 超时会自动往 Done() 返回的 channel 里写入数据。
- 手动调用 CancelFunc 也会往 Done() 返回的 channel 里写入数据。
- 需要对 Context.Err() 进行判断区分是正常结束还是自动超时结束。
案例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main
import ( "context" "fmt" "math/rand" "time" )
func main() { rand.Seed(time.Now().UTC().UnixNano()) ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
go func(cancel func()) { defer cancel()
fmt.Println("Query Start...")
d := 1 + rand.Intn(6) fmt.Printf("Query use %dS\n", d) time.Sleep(time.Second * time.Duration(d)) fmt.Println("Query Finished!") }(cancel)
<-ctx.Done()
fmt.Printf("请求结束, Error: %v\n", ctx.Err()) }
|
正常输出:
1 2 3 4
| Query Start... Query use 2S Query Finished! 请求结束, Error: context canceled
|
超时输出:
1 2 3
| Query Start... Query use 4S 请求结束, Error: context deadline exceeded
|
Cancel Context 实现超时
Cancel Context 的特点:
- 手动调用 CancelFunc 会往 Done() 返回的 channel 里写入数据。
- 使用 time.After() 实现超时。
- 区分是正常结束还是超时结束比较容易。
- 与 Timeout Context 相比,需要使用 select 进行超时判的。
案例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package main
import ( "context" "fmt" "math/rand" "time" )
func main() { rand.Seed(time.Now().UTC().UnixNano()) ctx, cancel := context.WithCancel(context.Background())
go func(cancel func()) { defer cancel()
fmt.Println("Query Start...")
d := 1 + rand.Intn(6) fmt.Printf("Query use %dS\n", d) time.Sleep(time.Second * time.Duration(d)) fmt.Println("Query Finished!") }(cancel)
select { case <-ctx.Done(): fmt.Printf("请求正常结束, Error: %v\n", ctx.Err()) case <-time.After(time.Second * 3): fmt.Println("超时") } fmt.Printf("Error: %v\n", ctx.Err()) }
|
正常输出:
1 2 3 4 5
| Query Start... Query use 2S Query Finished! 请求正常结束, Error: context canceled Error: context canceled
|
超时输出:
1 2 3 4
| Query Start... Query use 5S 超时 Error: <nil>
|
手动操作 channel 实现超时
手动操作 channel 实现超时的特点:
- 与 Cancel Context 实现的超时很相似,也是使用 select 来进行超时判的。
- 但是在并发场景的时候,手动 channel 实现超时就比较麻烦,Context 可以很好的简化代码。
案例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package main
import ( "fmt" "math/rand" "time" )
func main() { rand.Seed(time.Now().UTC().UnixNano()) doneC := make(chan struct{})
go func(quit chan<- struct{}) { defer close(doneC)
fmt.Println("Query Start...")
d := 1 + rand.Intn(6) fmt.Printf("Query use %dS\n", d) time.Sleep(time.Second * time.Duration(d)) fmt.Println("Query Finished!") }(doneC)
select { case <-doneC: fmt.Println("请求正常结束") case <-time.After(time.Second * 3): fmt.Println("超时") } }
|
正常输出:
1 2 3 4
| Query Start... Query use 1S Query Finished! 请求正常结束
|
超时输出:
1 2 3
| Query Start... Query use 6S 超时
|