Golang Context使用案例

Golang Context使用案例

go 服务器中,对于每个请求的 request 都是在单独的 goroutine 中进行的,处理一个 request 也可能涉及多个 goroutine 之间的交互, 使用 context 可以使开发者方便的在这些 goroutine 里传递 request 相关的数据、取消 goroutine 的 signal 或截止日期。

WithCancel例子

结构

229 func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { 230 c := newCancelCtx(parent) 231 propagateCancel(parent, &c) 232 return &c, func() { c.cancel(true, Canceled) } 233 } 234 235 // newCancelCtx returns an initialized cancelCtx. 236 func newCancelCtx(parent Context) cancelCtx { 237 return cancelCtx{ 238 Context: parent, 239 done: make(chan struct{}), 240 } 241 }

案例

package main import ( "context" "fmt" ) func main() { fmt.Println("嗨客网(www.haicoder.net)") gen := func(ctx context.Context) <-chan int { dst := make(chan int) n := 1 go func() { for { select { case <-ctx.Done(): return // returning not to leak the goroutine case dst <- n: n++ } } }() return dst } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers for n := range gen(ctx) { fmt.Println("Received", n) if n == 5 { break } } }

运行后,如下图所示:

47_Go语言Context使用.png

此示例演示使用一个可取消的上下文,以防止 goroutine 泄漏。示例函数结束时,defer 调用 cancel 方法,gen goroutine 将返回而不泄漏。

WithDeadline例子

结构

369 func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { 370 if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { 371 // The current deadline is already sooner than the new one. 372 return WithCancel(parent) 373 } 374 c := &timerCtx{ 375 cancelCtx: newCancelCtx(parent), 376 deadline: deadline, 377 } ......

案例

package main import ( "context" "fmt" "time" ) func main() { fmt.Println("嗨客网(www.haicoder.net)") d := time.Now().Add(50 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) // Even though ctx will be expired, it is good practice to call its // cancelation function in any case. Failure to do so may keep the // context and its parent alive longer than necessary. defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println("Context Done,", ctx.Err()) } }

运行后,如下图所示:

48_Go语言Context使用.png

可以清晰的看到,当派生出的子 Context 的 deadline 在父 Context 之后,直接返回了一个父 Context 的拷贝。故语义上等效为父。

WithDeadline 的最后期限调整为不晚于 d 返回父上下文的副本。如果父母的截止日期已经早于 d,WithDeadline (父,d) 是在语义上等效为父。返回的上下文完成的通道关闭的最后期限期满后,返回的取消函数调用时,或当父上下文完成的通道关闭,以先发生者为准。

WithTimeout例子

结构

436 func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { 437 return WithDeadline(parent, time.Now().Add(timeout)) 438 }

案例

package main import ( "context" "fmt" "time" ) func main() { fmt.Println("嗨客网(www.haicoder.net)") // Pass a context with a timeout to tell a blocking function that it // should abandon its work after the timeout elapses. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() select { case <-time.After(1 * time.Second): fmt.Println("overslept") case <-ctx.Done(): fmt.Println("Context Timeout", ctx.Err()) // prints "context deadline exceeded" } }

运行后,如下图所示:

49_Go语言Context使用.png

WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。

WithValue例子

结构

454 func WithValue(parent Context, key, val interface{}) Context { 454 if key == nil { 455 panic("nil key") 456 } 457 if !reflect.TypeOf(key).Comparable() { 458 panic("key is not comparable") 459 } 460 return &valueCtx{parent, key, val} 461 }

案例

package main import ( "context" "fmt" ) func main() { fmt.Println("嗨客网(www.haicoder.net)") type favContextKey string f := func(ctx context.Context, k favContextKey) { if v := ctx.Value(k); v != nil { fmt.Println("found value:", v) return } fmt.Println("key not found:", k) } k := favContextKey("language") ctx := context.WithValue(context.Background(), k, "Go") f(ctx, k) f(ctx, favContextKey("color")) }

运行后,如下图所示:

50_Go语言Context使用.png

WithValue 返回的父与键关联的值在 val 的副本。使用上下文值仅为过渡进程和 Api 的请求范围的数据,而不是将可选参数传递给函数。

提供的键必须是可比性和应该不是字符串类型或任何其他内置的类型以避免包使用的上下文之间的碰撞。WithValue 用户应该定义自己的键的类型。为了避免分配分配给接口 {} 时,上下文键经常有具体类型结构 {}。另外,导出的上下文关键变量静态类型应该是一个指针或接口。

Golang Context使用案例总结

在 go 服务器中,对于每个请求的 request 都是在单独的 goroutine 中进行的,处理一个 request 也可能涉及多个 goroutine 之间的交互, 使用 context 可以使开发者方便的在这些 goroutine 里传递 request 相关的数据、取消 goroutine 的 signal 或截止日期。