当然,go语言的超时控制肯定不止4种方法,起这个标题是我的一种自嘲,让我想起了孔乙己说的茴香的茴有4种写法。
本文写的4种方程都借助于同一个套路:
1 2 3 4 5 6 7 8 9 10 11 12 | workDoneCh := make( chan struct {}, 1) go func () { LongTimeWork() //这是我们要控制超时的函数 workDoneCh <- struct {}{} }() select { //下面的case只执行最早到来的那一个 case <-workDone: //LongTimeWork运行结束 fmt.Println( "LongTimeWork return" ) case <-timeoutCh: //timeout到来 fmt.Println( "LongTimeWork timeout" ) } |
比如我们希望100ms超时,那么100ms之后<-timeoutCh这个读管道的操作需要解除阻塞,而解除阻塞有2种方式,要么有人往管道里写入了数据,要么管道被close了。下面的4种方法就围绕<-timeoutCh如何解除阻塞展开。
式一:
这种方式最简单直接
1 2 3 4 5 | timeoutCh := make( chan struct {}, 1) go func () { time.Sleep(100 * time.Millisecond) timeoutCh <- struct {}{} }() |
式二:
不需要像方式一那样显式地创建一个timeoutCh管道,借助于time.After(duration),这个函数会返回一个管道,并且经过一段时间duration后它还会自动向管道send一个数据。
1 2 3 4 5 6 | select { //下面的case只执行最早到来的那一个 case <-workDone: //LongTimeWork运行结束 fmt.Println( "LongTimeWork return" ) case <-time.After(100 * time.Millisecond): //timeout到来 fmt.Println( "LongTimeWork timeout" ) } |
比式一优雅简洁了不少。
式三:
go语言Context是一个接口,它的Done()成员方法返回一个管道。
1 2 3 4 5 | type Context interface { Deadline() (deadline time.Time, ok bool) Done() <- chan struct {} Value(key interface {}) interface {} } |
cancelCtx是Context的一个具体实现,当调用它的cancle()函数时,会关闭Done()这个管道,<-Done()会解除阻塞。
1 2 3 4 5 6 7 8 9 10 11 | ctx, cancel := context.WithCancel(context.Background()) go func () { time.Sleep(100 * time.Millisecond) cancel() }() select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-ctx.Done(): //ctx.Done()是一个管道,调用了cancel()都会关闭这个管道,然后读操作就会立即返回 fmt.Println( "LongTimeWork timeout" ) } |
式四:
跟式三类似,timerCtx也是Context的一个具体实现,当调用它的cancle()函数或者到达指定的超时时间后,都会关闭Done()这个管道,<-Done()会解除阻塞。
1 2 3 4 5 6 7 | ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*100) select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-ctx.Done(): //ctx.Done()是一个管道,context超时或者调用了cancel()都会关闭这个管道,然后读操作就会立即返回 fmt.Println( "LongTimeWork timeout" ) } |
总体来看,式二和式四的代码量是最少的。最后附上完整代码:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | package main import ( "context" "fmt" "time" ) const ( WorkUseTime = 500 * time.Millisecond Timeout = 100 * time.Millisecond ) //模拟一个耗时较长的任务 func LongTimeWork() { time.Sleep(WorkUseTime) return } //模拟一个接口处理函数 func Handle1() { deadline := make( chan struct {}, 1) workDone := make( chan struct {}, 1) go func () { //把要控制超时的函数放到一个协程里 LongTimeWork() workDone <- struct {}{} }() go func () { //把要控制超时的函数放到一个协程里 time.Sleep(Timeout) deadline <- struct {}{} }() select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-deadline: fmt.Println( "LongTimeWork timeout" ) } } //模拟一个接口处理函数 func Handle2() { workDone := make( chan struct {}, 1) go func () { //把要控制超时的函数放到一个协程里 LongTimeWork() workDone <- struct {}{} }() select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-time.After(Timeout): fmt.Println( "LongTimeWork timeout" ) } } //模拟一个接口处理函数 func Handle3() { //通过显式sleep再调用cancle()来实现对函数的超时控制 ctx, cancel := context.WithCancel(context.Background()) workDone := make( chan struct {}, 1) go func () { //把要控制超时的函数放到一个协程里 LongTimeWork() workDone <- struct {}{} }() go func () { //100毫秒后调用cancel(),关闭ctx.Done() time.Sleep(Timeout) cancel() }() select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-ctx.Done(): //ctx.Done()是一个管道,调用了cancel()都会关闭这个管道,然后读操作就会立即返回 fmt.Println( "LongTimeWork timeout" ) } } //模拟一个接口处理函数 func Handle4() { //借助于带超时的context来实现对函数的超时控制 ctx, cancel := context.WithTimeout(context.Background(), Timeout) defer cancel() //纯粹出于良好习惯,函数退出前调用cancel() workDone := make( chan struct {}, 1) go func () { //把要控制超时的函数放到一个协程里 LongTimeWork() workDone <- struct {}{} }() select { //下面的case只执行最早到来的那一个 case <-workDone: fmt.Println( "LongTimeWork return" ) case <-ctx.Done(): //ctx.Done()是一个管道,context超时或者调用了cancel()都会关闭这个管道,然后读操作就会立即返回 fmt.Println( "LongTimeWork timeout" ) } } func main() { Handle1() Handle2() Handle3() Handle4() } |
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/p/15170880.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决