golang语言——Context

234 阅读2分钟

go语言协程同步可以通过sync.WaitGroup或chan来实现。chan同步的方式只适用于简单的情形。当协程较多时,通过一个协程创建出更多独立的协程,需要通知协程结束时,chan的方式会非常复杂。为此,go内置的context包进行了封装,提供了多协程安全的取消或通知机制。

package main

import (
	"context"
	"fmt"
	"time"
)

var key string = "name"

func watch(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(ctx.Value(key), "协程将要停止")
			return
			default:
				fmt.Println(ctx.Value(key), "协程运行中")
				time.Sleep(2*time.Second)
		}
	}
}

func main() {
	ctx,cancel := context.WithCancel(context.Background())

	valuectx1 := context.WithValue(ctx, key, "【协程1】")
	go watch(valuectx1)
	valuectx2 := context.WithValue(ctx, key, "【协程2】")
	go watch(valuectx2)
	valuectx3 := context.WithValue(ctx, key, "【协程3】")
	go watch(valuectx3)

	time.Sleep(10*time.Second)
	fmt.Println("通知协程停止")
	cancel()
	time.Sleep(2*time.Second)
}

context.WithTimeout用于设定超时,当超时时间到时,可以用于取消处理逻辑的协程:

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx,cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	go handle(ctx, 1500*time.Millisecond)
	select {
	case <-ctx.Done():
		fmt.Println("main", ctx.Err())
	}
}

func handle(ctx context.Context, duration time.Duration) {
	select {
	case <-ctx.Done():
		fmt.Println("handle", ctx.Err())
		case <-time.After(duration):
			fmt.Println("process request with", duration)
	}

}

注意,上述代码打印出来的结果可能是:

didi@bogon awesomeProject1 % go run test7.go 
main context deadline exceeded
handle context deadline exceeded
didi@bogon awesomeProject1 % go run test7.go 
main context deadline exceeded
didi@bogon awesomeProject1 % go run test7.go 
handle context deadline exceeded
main context deadline exceeded

因为多线程并行运行,无法保证最终的标准输出顺序,除非加同步锁。

context.WithDeadline用于设定截止日期,当截止时间到达,需要同步的协程可以通过读取ctx.Done()通道退出。

package main

import (
	"context"
	"fmt"
	"time"
)

func worker(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(name, "is stopping now.")
			return
		default:
			fmt.Println(name, "is working now.")
			time.Sleep(1*time.Second)
		}
	}

}

func main() {
	ctx,cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))

	go worker(ctx, "协程1")

	time.Sleep(6*time.Second)
	cancel()
	time.Sleep(2*time.Second)
}

输出:

didi@bogon awesomeProject1 % go run test9.go 
协程1 is working now.
协程1 is working now.
协程1 is working now.
协程1 is working now.
协程1 is working now.
协程1 is stopping now.

这里演示的是超时通知协程关闭的情况。
如果main协程中time.Sleep(6time.Second)改为time.Sleep(3time.Second),则输出为:

didi@bogon awesomeProject1 % go run test9.go 
协程1 is working now.
协程1 is working now.
协程1 is working now.
协程1 is stopping now.

这里演示的是尚未超时,一个协程主动通知另一个协程关闭的情况。