这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
context
context为Go中的一个标准库, 专门用来处理多个协程之间的控制问题, 比如协程的取消, 协程运行截止时间, 协程运行的超时时间, 协程之间的数据传输等.
当一个协程被退出时, 我们可能需要所有的由它开启的协程全部退出, 释放这些已经不再使用的资源, 就需要用到context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline() 返回当前context截止时间, 如果有返回true, 没有返回false
Done() 当context取消时, 会调用该方法, 向通道写入数据, 由此可以判断是否要退出当前协程的执行
Err() 返回当前context执行状态的信息, context被取消时Err()有有数据
Value() 用于不同协程之间的数据传输
context使用规则
- context要作为函数的第一个参数使用, 不要试图将它放入结构体中
- 当方法或函数需要一个context时, 不要传入nil, 可以传入context.TODO
- context可以传入到不同的协程中, 并且在多个协程中是安全的
- 当context传入到子协程中时, 需要在子协程中监控conext.Done()返回的通道, 当收到通知时, 停止当前子协程的运行, 释放资源并返回到上层
sync.WaitGroup方式
全局变量方式
var (
wg sync.WaitGroup
exit bool
)
// 采用全局变量的方式结束协程
func globalFunc() {
wg.Add(1)
go globalFunc2()
// 当main函数运行5s后, 退出协程f
time.Sleep(time.Second * 5)
// 这时需要将全局变量exit设置true
// 当协程访问这个变量时, 就知道是该退出的时候了
exit = true
wg.Wait()
}
func globalFunc2() {
// 每隔1s打印do...
for true {
fmt.Println("globalFunc2...")
time.Sleep(time.Second)
// 如果为false表明继续执行
// 如果为true表明, 启动这个协程的上级协程, 要求它需要退出了
if exit {
fmt.Println("globalFunc2 done...")
break
}
}
wg.Done()
}
通道方式
func chanFunc() {
wg.Add(1)
var b = make(chan bool)
go chanFunc2(b)
// 当main函数运行5s后, 退出协程f
time.Sleep(time.Second * 5)
// 向通道写入数据, 表明调用协程要求子协程退出
b <- true
wg.Wait()
}
func chanFunc2(c chan bool) {
// 每隔1s打印do...
for true {
fmt.Println("chanFunc2...")
time.Sleep(time.Second)
select {
// 当能从通道中读取数据时, 表明要求当前协程要退出执行了
case <- c:
fmt.Println("chanFunc2 done!")
wg.Done()
break
default:
}
}
}
context.WithCancel的使用
func ctxCancelFunc() {
wg.Add(1)
// 使用withCancel来创建一个context实例
// 它的根content为context.Background()
ctx, cancelF := context.WithCancel(context.Background())
go ctxCancelFunc2(ctx)
// 当main函数运行5s后, 通知协程f退出
time.Sleep(time.Second * 5)
// 调用创建ctx时的, cancelF函数来通知子协程退出
cancelF()
wg.Wait()
}
// 子协程
func ctxCancelFunc2(ctx context.Context) {
// 每隔1s打印do...
for true {
fmt.Println("ctxCancelFunc2...")
time.Sleep(time.Second)
select {
// 当上级调用cancel方法后, 这里会收到通知
case <- ctx.Done():
fmt.Println("ctxCancelFunc2 done...")
wg.Done()
break
default:
}
}
}
context.WithTimeout的使用
func timeoutFunc() {
// 定义一个时间间隔
d := time.Second * 5
// 使用context.WithTimeout来创建一个context
// 它的根context为context.Background()
ctx, cancelF := context.WithTimeout(context.Background(), d)
// 虽然到了一定时间会自动调用cancel函数
// 但是为了保证安全, 在当前协程执行完毕的时候, 还是需要手动调用一下的
defer cancelF()
wg.Add(1)
go timeoutFunc2(ctx)
// 当前协程执行时间为10s
// 但是当context的时间到了之后, 就会取消子协程的执行
time.Sleep(time.Second * 8)
}
func timeoutFunc2(ctx context.Context) {
defer func() {
fmt.Println("timeoutFunc2 done!")
}()
for {
fmt.Println("timeoutFunc2 ...")
time.Sleep(time.Second)
select {
// 当子协程收到通知后, 退出当前协程的执行
case <-ctx.Done():
wg.Done()
return
default:
}
}
}
context.WithDeadline的使用
func deadlineFunc() {
// 定义子协程运行截止时间
dl := time.Now().Add(time.Second * 5)
// 通完context.WithDeadline创建一个context实例
ctx, cancelF := context.WithDeadline(context.Background(), dl)
// 虽然到期自动启用
// 但是为了保证运行安全, 在当前协程退出时间, 还是需要手动调用一下的
defer cancelF()
wg.Add(1)
go deadlineFunc2(ctx)
// 当前协程支持时间, 大于子协程要退出的时间
time.Sleep(time.Second * 8)
wg.Wait()
}
func deadlineFunc2(ctx context.Context) {
defer func() {
fmt.Println("deadlineFunc2 done!")
}()
for {
fmt.Println("deadlineFunc2 ...")
time.Sleep(time.Second)
select {
// 当收到context的取消通知后, 退出当前子协程的执行
case <-ctx.Done():
wg.Done()
return
default:
}
}
}
context.WithValue的使用
func valueFunc() {
// 使用context.WithValue创建一个conext
// 设置要通过context要传递的键值
ctx := context.WithValue(context.Background(), "key", "value")
go valueFunc2(ctx)
time.Sleep(time.Second)
}
func valueFunc2(ctx context.Context) {
// 通过context提供的Value方法来获取数据
v := ctx.Value("key")
fmt.Println("value: ", v)
}