// 控制并发有三种经典的方式:一种是WaitGroup,另外一种是Chan通知,还有一种是Content.
// WaitGroup // WaitGroup适用于好多个Goroutine协同做一件事,每个Goroutine只做这件事的一部分。 func main1() { var wg sync.WaitGroup
wg.Add(2)
go func() { time.Sleep(2 * time.Second) fmt.Println("1号完成") wg.Done() }()
go func() { time.Sleep(2 * time.Second) fmt.Println("2号完成") wg.Done() }()
wg.Wait() fmt.Println("都完成,结束") }
// Chan 通知 // Goroutine启动以后我们是无法控制它的,大部分情况是等待它自己结束,如果这个Goroutine是一个不会自己结束的后台Goroutine? // 使用全局变量,后台Goroutine去不停检查是否被通知关闭 // 使用Chan + select // 弊端:如果有很多Goroutine都需要控制结束怎么办?这些Goroutine又衍生了其他的Goroutine怎么办,一层层的无穷尽的Goroutine,非常复杂!
func main2() { stop := make(chan bool) go func() { for { select { case <-stop: fmt.Println("停止了") return default: fmt.Println("进行中") time.Sleep(2 * time.Second) } } }()
time.Sleep(10 * time.Second) fmt.Println("通知进行停止") stop <- true
// 是否停止 time.Sleep(5 * time.Second) }
// Context Goroutine的上下文 // 使用 Context 跟踪 Goroutine,以便进行控制,优雅解决了Goroutine启动后不可控的问题
// 使用Context控制一个Goroutine func main3() { background := context.Background() // 返回一个空的Context,这个空的Context一般用于整个Context树的根节点 ctx, cancel := context.WithCancel(background) // 创建一个可取消的子Context,传递给Goroutine使用,使用子Context跟踪这个Goroutine go func(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("停止了") return default: fmt.Println("进行中") time.Sleep(2 * time.Second) } } }(ctx)
time.Sleep(10 * time.Second) fmt.Println("通知进行停止") cancel() // 调用它发送取消指令,然后Goroutine会接收到信号
// 查看是否结束 time.Sleep(5 * time.Second) }
// 使用Context控制多个Goroutine func main4() { ctx, cancel := context.WithCancel(context.Background())
go do(ctx, "1号") go do(ctx, "2号") go do(ctx, "3号")
time.Sleep(10 * time.Second) fmt.Println("通知进行停止") cancel() // 所有基于这个Context或者衍生的子Context都会收到通知
// 查看是否结束 time.Sleep(5 * time.Second) }
func do(ctx context.Context, name string) { for { select { case <-ctx.Done(): fmt.Println(ctx.Err()) fmt.Println(name + "停止了") return default: fmt.Println(name + "进行中") time.Sleep(2 * time.Second) } } }
// WithValue传递元数据 var key string = "name"
func main() { ctx, cancel := context.WithCancel(context.Background()) // 附加值 valueCtx := context.WithValue(ctx, key, "1号")
go watch2(valueCtx)
time.Sleep(10 * time.Second) fmt.Println("通知进行停止") cancel() time.Sleep(5 * time.Second) }
func watch2(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) }
} }
// Context 使用原则 // 不要把Context放在结构体中,要以参数的方式传递 // 以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。 // 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用(context.TODO) // Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递 // Context是线程安全的,可以放心的在多个goroutine中传递
|
请发表评论