15、Context

一、常规gorutine控制

  • 控制并发的两种方式
    • 使用WaitGroup:多个Goroutine执行同一件事情
    • 使用Context
  • WaitGroup简单示例:控制多个Go程,全部执行结束后,主线程才允许退出
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		time.Sleep(2 * time.Second)
		fmt.Println(time.Now().Unix(), " -> job one down")
		wg.Done()
	}()

	go func() {
		time.Sleep(1 * time.Second)
		fmt.Println(time.Now().Unix(), " -> job two down")
		wg.Done()
	}()

	fmt.Println(time.Now().Unix(), " -> before block")
	wg.Wait() //阻塞等待所有的job执行结束
	fmt.Println(time.Now().Unix(), " -> after block")
}

在这里插入图片描述

  • Channel+select控制gorutine
package main

import (
	"fmt"
	"time"
)

func main() {
	stop := make(chan bool)

	go func() {
		for {
			select {
			case <-stop:
				fmt.Println(time.Now().Unix(), " -> got the stop channel")
				return

			default:
				fmt.Println(time.Now().Unix(), " -> still working")
				time.Sleep(1 * time.Second)
			}
		}
	}()

	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Unix(), " -> stop the gorutine")
	stop <- true
	time.Sleep(5 * time.Second)
}

在这里插入图片描述

二、context控制groutine

  • 多个Gorutine或者多个Gorutine内部又有Goroutine的情况如何控制:使用context
    在这里插入图片描述

1 - 使用context控制单个gorutine

  • 使用context控制单个gorutine
package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Println(time.Now().Unix(), " -> got the stop channel")
				return
			default:
				fmt.Println(time.Now().Unix(), " -> still working")
				time.Sleep(1 * time.Second)
			}
		}
	}()

	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Unix(), " -> stop the gorutine")
	cancel()
	time.Sleep(5 * time.Second)
}

2 - context创建

  • context.Background():函数返回一个空context,通过 new(emptyCtx) 语句初始化,指向私有结构体 context.emptyCtx 的指针
  • context.TODO():函数返回一个空context,通过 new(emptyCtx) 语句初始化,指向私有结构体 context.emptyCtx 的指针
  • go源码
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
	return background
}

// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
	return todo
}
  • Background和TODO区别:context.Background 和 context.TODO 函数其实也只是互为别名,没有太大的差别,它们只是在使用和语义上稍有不同
    • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来
    • context.TODO 应该只在不确定应该使用哪种上下文时使用

在多数情况下,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递

3 - context函数

  • context函数
    • 以下4个方法的参数partent,就是父Context,要基于这个父Context创建出子Context;这种方式可以理解为子Context对父Context的继承,也可以理解为基于父Context的衍生
    • 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO
    • Context是线程安全的,可以放心的在多个goroutine中传递。同一个Context可以传给使用其的多个goroutine,且Context可被多个goroutine同时安全访问
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}
func WithValue(parent Context, key, val any) Context
  • WithCancel:传递一个父Context作为参数,返回子Context,以及一个取消函数用来取消Context(永远不要传递取消函数)
  • WithDeadline:WithDeadline和WithCancel,多传递一个截止时间参数,意味着到了这个时间点,会自动取消Context,当然我们也可以不等到这个时候,可以提前通过取消函数进行取消
  • WithTimeout:查看源码实现可以看到和WithDeadline其实是一样的,WithDeadline是绝对时间,WithTimeout是以当前时间为起始的持续时间
  • WithValue:WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到
    • 值 val 与 key 关联,并通过 context 树与 context 一起传递
    • 一旦获得带有值的 context,从中派生的任何 context 都会获得此值
    • WithValue返回valueCtx的指针,valueCtx包含三个成员context、key、val
    • 不建议使用 context 值传递关键参数
    • key不建议使用string或其他内置类型,所以建议自定义key类型

4 - Context接口

  • Deadline:返回 context.Context 被取消的时间,也就是完成工作的截止日期
  • Done:返回一个 Channel,这个 Channel 会在当前工作完成或者上下文被取消之后关闭,多次调用 Done 方法会返回同一个 Channel
  • Err:返回 context.Context 结束的原因,它只会在 Done 返回的 Channel 被关闭时才会返回非空的值
    • 如果 context.Context 被取消,会返回 Canceled 错误
    • 如果 context.Context 超时,会返回 DeadlineExceeded 错误
  • Value:从 context.Context 中获取键对应的值,对于同一个上下文来说,多次调用 Value 并传入相同的 Key 会返回相同的结果,该方法可以用来传递请求特定的数据
type Context interface {
	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{}

	Err() error

	Value(key any) any
}

5 - 使用context控制多个gorutine停止

package main

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

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

	go worker(ctx, "node01")
	go worker(ctx, "node02")
	go worker(ctx, "node03")
	time.Sleep(5 * time.Second)
	fmt.Println(time.Now().Unix(), " -> stop the gorutine")
	cancel()
	time.Sleep(5 * time.Second)
}

func worker(ctx context.Context, name string) {
	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Println(time.Now().Unix(), " -> ", name, " got the stop channel")
				return
			default:
				fmt.Println(time.Now().Unix(), " -> ", name, " still working")
				time.Sleep(1 * time.Second)
			}
		}
	}()
}

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值