context.WithValue(parent Context, key, val interface{}) Context
这个函数接受一个上下文并返回一个派生的上下文,其中值 val 与 key 相关联,并与上下文一起经过上下文树。这意味着一旦你得到一个带有值的上下文,任何从它派生的上下文都会得到这个值。该值是不可变的,因此是线程安全的。
提供的键必须是可比较的,并且不应该是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该为键定义自己的类型。为避免在分配给 interface{} 时进行分配,上下文键通常具有具体类型 struct{}。或者,导出的上下文键变量的静态类型应该是指针或接口。
package main import ( "context" "fmt" ) type contextKey string func main() { var authToken contextKey = "auth_token" ctx := context.WithValue(context.Background(), authToken, "Hello123456") fmt.Println(ctx.Value(authToken)) }
运行该代码:
$ go run . Hello123456
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
此函数接受父上下文并返回派生上下文以及 CancelFunc 类型的取消函数。在这个派生上下文中,添加了一个新的 Done channel,该 channel 在调用 cancel 函数或父上下文的 Done 通道关闭时关闭。
要记住的一件事是,我们永远不应该在不同的函数或层之间传递这个 cancel ,因为它可能会导致意想不到的结果。创建派生上下文的函数应该只调用取消函数。
下面是一个使用 Done 通道演示 goroutine 泄漏的示例:
package main import ( "context" "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithCancel(context.Background()) defer cancel() for char := range randomCharGenerator(ctx) { generatedChar := string(char) fmt.Printf("%v\n", generatedChar) if generatedChar == "o" { break } } } func randomCharGenerator(ctx context.Context) <-chan int { char := make(chan int) seedChar := int('a') go func() { for { select { case <-ctx.Done(): fmt.Printf("found %v", seedChar) return case char <- seedChar: seedChar = 'a' + rand.Intn(26) } } }() return char }
运行结果:
$ go run . a m q c l t o
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
此函数从其父级返回派生上下文,当期限超过或调用取消函数时,该派生上下文将被取消。例如,您可以创建一个在未来某个时间自动取消的上下文,并将其传递给子函数。当该上下文由于截止日期用完而被取消时,所有获得该上下文的函数都会收到通知停止工作并返回。如果 parent 的截止日期已经早于 d,则上下文的 Done 通道已经关闭。
下面是我们正在读取一个大文件的示例,该文件的截止时间为当前时间 2 毫秒。我们将获得 2 毫秒的输出,然后将关闭上下文并退出程序。
package main import ( "bufio" "context" "fmt" "log" "os" "time" ) func main() { // context with deadline after 2 millisecond ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(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) } } }