目录
- 一、常规gorutine控制
- 二、context控制groutine
- 1 - 使用context控制单个gorutine
- 2 - context创建
- 3 - context函数
- 4 - Context接口
- 5 - 使用context控制多个gorutine停止
一、常规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)
}
}
}()
}