在并发程序中, 由于超时、取消、或系统的其他部分的故障 往往需要抢占操作。
done channel 可以在你的程序中流动并取消所有阻塞的并发操作。 看起来不错,但是还不完美,还不是很够用。
什么样的功能还需要呢?什么样功能才算完美呢?
如果我们可以在简单的通知上附加传递额外的信息; 如 为什么取消发生, 函数是否有需要完成的最后期限(超时), 这些情况下这些功能 非常有用。
context 包有如下方法:
todo
context(type 类型): 有:
一个Deadline 函数,用于指示在一定时间之后goroutine 是否被取消,
一个 Err方法, 如果goroutine 被取消,将返回非零。
一个Value 方法,
函数中取消有三种情况:
1。 goroutine 的 父 goroutine 可能想要取消它。
2。 一个goroutine 可能想要取消它的 子goroutine
3。 goroutine 中呢任何阻塞操作都必须是可抢占的, 以便它可以被取消。
下面看看 之前 使用 do channel 方式 有什么优缺点
func printGreeting(done <-chan interface{}) error{
greeting, err := genGreeting(done)
if err != nil{
return err
}
fmt.Printf("%s world!\n", greeting)
return nil
}
func genGreeting(done <-chan interface{})(string, error){
switch locale, err := locale(done);{
case err != nil:
return "", err
case locale == "EN/US":
return "hello", nil
}
return "", fmt.Errorf("unsupported locale")
}
func locale(done <-chan interface{})(string, error){
select{
case <-done :
return "", fmt.Errorf("canceled")
case <-time.After(1*time.Minute):
}
return "EN/US", nil
}
func printFarewell(done <-chan interface{}) error{
farewell, err := genFarewell(done)
if err != nil{
return err
}
fmt.Printf("%s world!\n", farewell)
return nil
}
func genFarewell(done <-chan interface{})(string, error){
switch locale,err := locale(done);{
case err != nil:
return "", err
case locale == "EN/US":
return "goodbye", nil
}
return "", fmt.Errorf("unsupproted locale")
}
func main() {
var wg sync.WaitGroup
done := make(chan interface{})
defer close(done)
wg.Add(1)
go func() {
defer wg.Done()
if err := printGreeting(done); err != nil{
fmt.Println("%v", err)
return
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := printFarewell(done); err != nil{
fmt.Printf("%v", err)
}
}()
wg.Wait()
}
假设有这样需求:
假设genGreeting 只想在放弃调用locale之前等待 1s(超过1s 就不想调用了),超时时间 1s;
如果 printGreeting 不成功,我们也想取消 对 printFarewall的调用。 毕竟不打招呼,说再见就没有意义了。使用do channel 难以做到这点,下面 看看context 能不能满足这样的需求?
func main() {
var wg sync.WaitGroup
//Background() 方法 返回一个空的上下文
// withCancel 包装它 为了 获取 cancel/取消 方法/功能
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wg.Add(1)
go func() {
defer wg.Done();
if err := printGreeting(ctx); err != nil{
fmt.Printf("cannot print greeting: %v\n", err)
//在这一行,如果打印问候出错,将 取消上下文; 或说对上下文 环境 执行 cancel, 这样使用这个上下文或者从这个上下文衍生出来的上下文。
//中 ct.Done() 将收到信号/收到值; 进而达到解除阻塞 的目的。
cancel()
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := printFarewell(ctx); err != nil{
fmt.Printf("cannot print farewell: %v\n", err)
}
}()
wg.Wait()
}
func printGreeting(ctx context.Context) error{
greeting, err := genGreeting(ctx)
if err != nil{
return err
}
fmt.Printf("%s world!\n", greeting)
return nil
}
func genGreeting(ctx context.Context)(string, error){
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
switch locale, err := locale(ctx);{
case err != nil :
return "", err
case locale == "EN/US":
return "hello", nil
}
return "", fmt.Errorf("unsupported locale")
}
func locale(ctx context.Context)(string, error){
select{
case <-ctx.Done():
return "", ctx.Err()
case <-time.After(1* time.Minute) :
}
return "EN/US", nil
}
func printFarewell(ctx context.Context) error{
farewell, err := genFarewell(ctx)
if err != nil{
return err
}
fmt.Printf("%s world!\n", farewell)
return nil
}
func genFarewell(ctx context.Context)(string, error){
switch locale, err := locale(ctx);{
case err != nil:
return "", err
case locale == "EN/US":
return "goodbye", nil
}
return "", fmt.Errorf("unsupported locale")
}
//cannot print greeting: context deadline exceeded
//cannot print farewell: context canceled
总结: cancel() 或者 时间到了(context.WithTimeout(ctx, 1*time.Second)) 都会 使 ctx.Done() 收到值, 解除阻塞
那么 Deadline 又是在什么情况下。或有那些需求时会用到?
func locale(ctx context.Context)(string, error){
if deadline, ok := ctx.Deadline(); ok{
if deadline.Sub(time.Now().Add(1*time.Minute)) <= 0 {
fmt.Println("end locale")
return "", context.DeadlineExceeded
}
}
select{
case <- ctx.Done():
return "", ctx.Err()
case <-time.After(1*time.Minute):
}
return "EN/US", nil
}
//end locale
//cannot print greeting: context deadline exceeded
//cannot print farewell: context canceled
/**
在这里我们检查我们的上下文是否提供了截止。 如果提供了,并且 能够确定 当前时间 再执行下去一定会超时, 那么程序就立刻返回,不往下走业务逻辑;
可以返回上下文包中的特定错误, 即 DeadlineExceeded
在调用下一个 成本很高的函数的程序中, 这可能会节省大量的时间,它允许 该函数立即失败,而不必等待实际的超时发生。 前提/适用的情况是 必须知道
下级调用图 需要多长时间,这个可能实践起来非常困难。
图:todo
实际上 超时时间设置 肯定大于 正常情况下 程序的执行时间;如 假如 locale 一般 要执行 1min ;那么超时时间 就是 1min + ;(本例子中 是为了看到
效果 才将 超时时间设置为1s)
决定 deadline 是否满足 <=0 的关键 在于 now位置(它是变化的);实际上 意思是: 如果前面执行的时间太长了(出现意外情况);那么后面也就没必要
再执行了,因为执行了肯定会超时的!
如何在上下文中存储数据以及如何检索数据:
func main(){
ProcessRequest("jane", "abc123")
}
func ProcessRequest(userID, authToken string){
ctx := context.WithValue(context.Background(), "userID", userID)
ctx = context.WithValue(ctx, "authToken", authToken)
HandleResponse(ctx)
}
func HandleResponse(ctx context.Context){
fmt.Printf("handling response for %v (%v)",
ctx.Value("userID"), ctx.Value("authToken"))
}
存储 使用 context.WithValue(....); 检索使用 ctx.Value()
正确使用这个功能 ,需要注意的是:/要满足的条件是:
1。 使用的键 必须满足 可比较特性/特点; 也就是 使用运算符== 和 != 在使用时要返回正确的结果
2。 返回值必须安全, 才能从多个goroutine 访问。
具体怎么做呢?
建议: 在包中定义一个自定义键 类型; 另一个包中定义另一个自定义键类型,就可以防止上下文中的冲突。
func main(){
type foo int
type bar int
m := make(map[interface{}]int)
m[foo(1)] = 1
m[bar(1)] = 2
fmt.Printf("%v", m)
}
//map[1:1 1:2]
由于 我们 不导出用于存储数据的key, 所以 通常是 必须导出 检索数据的函数。 这样 使用者 可以使用静态,类型安全的函数。
func main(){
ProcessRequest("jane", "abc123")
}
type ctxKey int
const(
ctxUserID ctxKey = iota
ctxAuthToken
)
func UserID(c context.Context) string{
return c.Value(ctxUserID).(string)
}
func AuthToken(c context.Context) string{
return c.Value(ctxAuthToken).(string)
}
func ProcessRequest(userID, authToken string){
ctx := context.WithValue(context.Background(), ctxUserID, userID)
ctx = context.WithValue(ctx, ctxAuthToken, authToken)
HandleResponse(ctx)
}
func HandleResponse(ctx context.Context){
fmt.Printf(" handling response for %v (auth:%v)", UserID(ctx), AuthToken(ctx))
}
//handling response for jane (auth:abc123)
context 的实例中该存储什么? 什么样数据存在context 中合适? 1。 2。 数据应该是不可变的 3。 数据应该 简单类型的 4。 数据就是数据,不是类型与方法 5。 尽量不要这么用数据;如 根据context 中包含 与不包含 算法而不同,这种情况就要考虑 了,要不要把这样数据放在context中?