Golang实现协程超时控制的方式总结
Golang对协程做超时的控制大概有两种方式,timer 和context。对于并发情况,又有不同的方式。
我们知道,go协程如果不做好处理,很容易造成内存泄漏。特别是在高并发的情况下,如果某一个 goroutine 由于意外退出,则会导致接收方一直阻塞,从而挂起主程序。
对goroutine做超时控制,能够有效避免这种情况发生。
Timer+Select
time.After 实现超时控制
- 利用 time.After 启动了一个异步的定时器,返回一个 channel,当超过指定的时间后,该 channel 将会接受到信号。
- 启动了子协程,函数执行结束后,将向 channel ch 发送结束信号。
- 使用 select 阻塞等待 done 或 time.After 的信息,若超时,输出timeout,若没有超时,则输出done。
func TestContext12(t *testing.T) { ch := make(chan struct{}, 1) go func() { fmt.Println("running...") time.Sleep(3 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("done") case <-time.After(2 * time.Second): fmt.Println("timeout") } }
output:
=== RUN TestContext12
running...
timeout
--- PASS: TestContext12 (3.01s)
PASS
这里要注意一个点,就是你创建ch 的时候,必须是带缓冲的。如果不带,在并发的情况下会怎样呢?
NewTimer
也可以新建一个NewTimer,timer.C返回也是一个channel
func TestContext5(t *testing.T) { timer := time.NewTimer(time.Duration(time.Millisecond * 900)) ch := make(chan struct{}, 1) go func() { fmt.Println("running...") time.Sleep(3 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("done") case <-timer.C: fmt.Println("timeout") } }
output:
=== RUN TestContext5
running...
timeout
--- PASS: TestContext5 (0.90s)
PASS
留一个坑
为什么不需要关闭ch 管道?什么时候关闭ch?
Context+Select
- 第二种方案是利用 context,context.WithTimeout。
- 它接受一个Context和一个超时时间作为参数,返回一个子Context和一个取消函数CancelFunc。
- 取消函数CancelFunc将释放子Context与之有关的资源,因此在子Context中的相关操作一旦完成,应该立即调用取消函数CancelFunc。
func TestContext6(t *testing.T) { ch := make(chan string) timeout, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() go func() { time.Sleep(time.Second * 3) ch <- "done" }() select { case res := <-ch: fmt.Println(res) case <-timeout.Done(): fmt.Println("timout", timeout.Err()) } }
- 利用 context.WithTimeou 返回启动了一个异步的定时器,返回一个 channel,当超过指定的时间后,该 channel 将会接受到信号。
- 启动了子协程,函数执行结束后,将向 channel ch 发送结束信号。
- 使用 select 阻塞等待 done 或 time.Done的信息,若超时,输出timeout,若没有超时,则输出done。
output:
=== RUN TestContext6
timout context deadline exceeded
--- PASS: TestContext6 (2.00s)
PASS
context+并发协程
并发协程使用WaitGroup阻塞主协程保证生产协程和消费协程正常执行完成。
func TestContext3(t *testing.T) { withTimeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*1) waitGroup := sync.WaitGroup{} waitGroup.Add(2) go func() { //协程1 time.Sleep(time.Second) fmt.Println("finished 1") waitGroup.Done() }() go func() { //协程2 time.Sleep(time.Second * 2) fmt.Println("finished 2") waitGroup.Done() }() go func() { select { case <-withTimeout.Done(): fmt.Println("timeout") return default: waitGroup.Wait() cancelFunc() fmt.Println("finished all") return //结束监听协程 } }() <-withTimeout.Done() }
- 开启协程1,等待1秒输出。协程2,等待2秒输出。
- context.WithTimeout等待1秒后返回函数。
- 1秒后,协程一输出。但是协程二没输出,主线程不等待了。直接返回。
output:
=== RUN TestContext3
finished 1
--- PASS: TestContext3 (1.01s)
PASS
这里多一个问题,留个坑!
goroutine如何超时控制并发输出a,b,c并且按照a,b,c的顺序输出?
到此这篇关于Golang实现协程超时控制的方式总结的文章就介绍到这了,更多相关Golang协程超时控制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
最新评论