func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
这个函数类似于 context.WithDeadline。不同之处在于它将持续时间作为输入而不是时间对象。此函数返回一个派生上下文,如果调用取消函数或超过超时持续时间,该上下文将被取消。
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))
。
package main import ( "bufio" "context" "fmt" "log" "os" "time" ) func main() { // context with deadline after 2 millisecond ctx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond) defer cancel() lineRead := make(chan string) var fileName = "sample-file.txt" file, err := os.Open(fileName) if err != nil { log.Fatalf("failed opening file: %s", err) } scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) // goroutine to read file line by line and passing to channel to print go func() { for scanner.Scan() { lineRead <- scanner.Text() } close(lineRead) file.Close() }() outer: for { // printing file line by line until deadline is reached select { case <-ctx.Done(): fmt.Println("process stopped. reason: ", ctx.Err()) break outer case line := <-lineRead: fmt.Println(line) } } }
如果父上下文的 Done 通道关闭,它最终将关闭所有派生的 Done 通道(所有后代),如:
package main import ( "context" "fmt" "time" ) func main() { c := make(chan string) go func() { time.Sleep(1 * time.Second) c <- "one" }() ctx1 := context.Context(context.Background()) ctx2, cancel2 := context.WithTimeout(ctx1, 2*time.Second) ctx3, cancel3 := context.WithTimeout(ctx2, 10*time.Second) // derives from ctx2 ctx4, cancel4 := context.WithTimeout(ctx2, 3*time.Second) // derives from ctx2 ctx5, cancel5 := context.WithTimeout(ctx4, 5*time.Second) // derives from ctx4 cancel2() defer cancel3() defer cancel4() defer cancel5() select { case <-ctx3.Done(): fmt.Println("ctx3 closed! error: ", ctx3.Err()) case <-ctx4.Done(): fmt.Println("ctx4 closed! error: ", ctx4.Err()) case <-ctx5.Done(): fmt.Println("ctx5 closed! error: ", ctx5.Err()) case msg := <-c: fmt.Println("received", msg) } }
在这里,由于我们在创建其他派生上下文后立即关闭 ctx2,因此所有其他上下文也会立即关闭,随机打印 ctx3、ctx4 和 ctx5 关闭消息。 ctx5 是从 ctx4 派生的,由于 ctx2 关闭的级联效应,它正在关闭。尝试多次运行,您会看到不同的结果。
使用 Background 或 TODO 方法创建的上下文没有取消、值或截止日期。
package main import ( "context" "fmt" ) func main() { ctx := context.Background() _, ok := ctx.Deadline() if !ok { fmt.Println("no dealine is set") } done := ctx.Done() if done == nil { fmt.Println("channel is nil") } }
总结
Context 是在 Go 中进行并发编程时最重要的工具之一。
- 不要将上下文存储在结构类型中;相反,将 Context 显式传递给需要它的每个函数。 Context 应该是第一个参数,通常命名为 ctx。
func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... }
- 不要不传递 nil 上下文,即使函数允许。如果不确定要使用哪个 Context,请传递 context.TODO。
- 仅使用上下文传递请求范围的数据。不要传递应该使用函数参数传递的数据。
- 始终寻找 goroutine 泄漏并有效地使用上下文来避免这种情况。
- 如果父上下文的 Done 通道关闭,它最终将关闭所有派生的 Done 通道(所有后代