Go 控制并发


// 控制并发有三种经典的方式:一种是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中传递
posted @ 2018-09-05 10:48  Zereker  阅读(739)  评论(0编辑  收藏  举报