以前接触了一个需求:提供一个接口,这个接口有一个超时时间,若是超时了返回超时异常;这个接口中调用其余的接口,若是调用超时了,全部请求所有结束。
在这个接口中,我使用了go协程去调用其余接口,因此不只涉及到请求的超时控制,并且还涉及到父协程对子协程的控制问题。在翻阅了一些资料以后,了解到了Context的基本知识。golang
Context是golang.org.pkg下的一个包,类型是接口类型。主要功能有函数
Context能够经过context.Background()
或者 context.TODO()
建立一个空的context。两个区别在于TODO
context能够进行派生,建立出子context。context有四种不一样的子context:
(1)WithCancel
:方法入参是一个context;返回值是一个带有新Done的父context的副本,以及cancel函数。当调用cancel函数时,通道将被关闭。关闭规则:会先关闭内部的接收通道;通道关闭了接收该通道的操做会当即返回(即done返回的通道),而且context会向它全部的子值传递信号,若是子context还有子context,那这个撤销信号就会一级一级传递下去。最后这个context会断开其与父context的链接。
(2)WithDeadline
和WithTimeout
(本次问题解决就使用的是这个):WithDeadline或者WithTimeout的功能极为类似。都是返回能够被撤销的Context子值。它们不但能够被手动撤销,还会依据在生成是给定的过时时间,自动地进行定时撤销。
WithDeadline是设置一个时间点,时间对上了就到期。WithTimeout是设置一段时间,好比几秒,过个这段时间,就超时。其实底层的WithTimeout也是经过WithDeadline实现的。WithTimeout的调用就等于WithDeadline(parent, time.Now().Add(timeout))(其中parent是父级context)
(3)WithValue
:入参是父级parent,存储的键和存储的值。返回的是一个带有数据的Context。这个Context是不能被撤销的。撤销的信号在传递的时候会跳过这个Context。spa
协程间共享数据主要使用的就是WithValue生成的子Context,这个Context存的值在其余的协程中也能读取到。能够用作数据的共享。code
主要用到的是WithDeadline生成的子Context以及Go中HttpClient请求中的context字段(下文会有描述)
*协程
其中,对于超时的判断,是根据Context中的Done管道判断的。若是超时了,则Done管道能够拿到东西。对象
使用http.NewRequest方法获取到的req,能够调用WithContext将定义好的WithTimeout类型的context放进去,以后调用&htto.Client{}.Do()方法便可。网上有一些博客中让手动调用transport中的CancelRequest方法,可是这个方法已经不被建议使用了。由于它不能取消Http/2的请求。blog
如今在代码中有一个私有化的roundTrip方法,会调用CancelRequest调用的cancelRequest方法。而这个roundTrip在transport中会在外面包一层RoundTrip方法,以后交给Client中的send方法进行调用。(具体能够进行源码的查阅)。因此如今经过Client的Do方法,能够自动完成请求的超时控制。接口
该调度模型亲测以后,确实能够实现请求的超时控制。只要在最外层设置超时时间时30s,只要过了30s,全部协程中的请求都会结束,对应的协程也会相应的结束;加上Client.Do方法,将超时控制变的更加简洁,后续会写专门写一篇关于http中Client的博客,详细讲解一下Client实现超时控制的原理。
若是大佬们发现这个调度模型有什么问题,或者我用的方法有什么BUG,欢迎留言指正。(来自Go萌新心里的渴望)ip