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.
这里演示的是尚未超时,一个协程主动通知另一个协程关闭的情况。