Goroutine 之间常用的通信方式有:
- 全局变量
- channel
- context 上下文
sync.WaitGroup
如果只是单纯的等待所有任务完成,可以使用 sync.WaitGroup
:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
fmt.Println("func run")
time.Sleep(time.Second)
wg.Done()
}()
}
wg.Wait()
fmt.Println("main done")
}
全局变量
简单,但是传递的数据只能一写多读。
package main
import (
"fmt"
"time"
)
var stop bool
func main() {
go f()
time.Sleep(2 * time.Second)
stop = false
time.Sleep(2 * time.Second)
fmt.Println("main done")
}
func f() {
for stop {
fmt.Println("still run")
time.Sleep(time.Second)
}
}
channel
CSP 并发编程模型(Communicating Sequential Process)。channel 在 Golang 中是核心类型。
Golang 的 select 机制在语言层面实现了类似 Linux 的 select 功能,可以监听多个文件描述符的读写事件,能够在事件发生时主动通知应用程序处理。 Golang 的 select 还可以设置 default,在监听的事件全部阻塞时执行。
package main
import (
"fmt"
"time"
)
func main() {
stop := make(chan bool)
go f(stop)
time.Sleep(2 * time.Second)
stop<-true
time.Sleep(2 * time.Second)
fmt.Println("main done")
}
func f(stop chan bool) {
for {
select {
case <-stop:
fmt.Println("done")
return
default:
fmt.Println("still run")
time.Sleep(time.Second)
}
}
}
context 上下文
Golang 的上下文是树状结构,通过 context.Background()
方法可以拿到上下文的根节点。常用方法有:
- func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
- func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
- func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
- func WithValue(parent Context, key, val interface{}) Context
WithCancel
子上下文可以调用 Done 方法(返回 channel,可以通过 select 读取)检测是否有父节点调用 cancel。上层节点的 cancel 调用会沿着上下文树的边向下通知到每一个子节点。
package main
import (
"fmt"
"time"
"context"
)
func main() {
ctx, myCancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("ctx Done")
return
default:
fmt.Println("goroutine continue")
time.Sleep(time.Second)
}
}
}()
time.Sleep(time.Second * 2)
myCancel()
time.Sleep(time.Second)
fmt.Println("main done")
}
当然,这里可以使用 sync.WaitGroup
来避免睡眠一段时间:
package main
import (
"fmt"
"time"
"context"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
ctx, myCancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
fmt.Println("ctx Done")
wg.Done()
return
default:
fmt.Println("goroutine continue")
time.Sleep(time.Second)
}
}
}()
time.Sleep(time.Second * 2)
myCancel()
wg.Wait()
fmt.Println("main done")
}
WithValue
package main
import (
"fmt"
"context"
)
type favContextKey string
func main() {
ctx := context.WithValue(context.Background(), favContextKey("hello"), "Go")
f := func(ctx context.Context, k favContextKey) {
if v := ctx.Value(k); v != nil {
fmt.Println("found value:", v)
return
}
fmt.Println("value not found:", k)
}
f(ctx, favContextKey("hello"))
f(ctx, favContextKey("color"))
}