golang控制并发(sync.WaitGroup和context.Context)

我们都知道,在golang中很容易实现并发,只需要一个关键字go就可以。但是在开启了多个goruntine之后,我们要如何去管理它们呢(包括停止退出goroutine,等待goruntine执行完成,继续让goruntine执行等)。这些我们在日常的业务中都可能碰到,下面就讲讲在不同的场景如何正确优雅地管理goruntine,即控制并发。

一,sync.WaitGroup

作用: 任务编排,等待多个 goroutine 全部完成

适用场景: 好多个 goroutine 协同做一件事情的时候,因为每个 goroutine 做的都是这件事情的一部分,只有全部的 goroutine 都完成,这件事情才算是完成,这是等待的方式。

func main() {
     
	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("好了,大家都干完了,放工")
}

这部分比较简单,不过多描述。更多参见在异步读写中使用sync.WaitGroup

二,context.Context

上面的场景是等待多个小任务都执行完毕之后退出。下面考虑这种场景:有一个持续性的任务,一般是常驻的不退出,比如说监控任务。但有时可能因为特殊需求,需要根据条件判断后让其退出。因为该监控任务和其他任务没有数据连接(channel之类的),但靠sync.WaitGroup是不能使其退出的。 很自然的,我们能想到应该通过一个媒介,来通知监控任务退出。全局变量可以充当这个媒介,但鉴于安全性很差,这里不予考虑(代码中一般来说不允许出现全局变量)。

chan通知

func main() {
     
	stop := make(chan bool)

	go func() {
     
		for {
     
			select {
     
			case <-stop:
				fmt.Println("监控退出,停止了...")
				return
			default:
				fmt.Println("goroutine监控中...")
				time.Sleep(2 * time.Second)
			}
		}
	}()

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	stop <- true
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

输出结果:

goroutine监控中...
goroutine监控中...
goroutine监控中...
goroutine监控中...
goroutine监控中...
可以了,通知监控停止
监控退出,停止了...

这种方式还是比较优雅的,但还是有很大的局限性。如果我们需要控制很多的goruntine结束怎么办,难道我们要搞出很多个chan?又或者子goruntine中又衍生出更多的子goruntine怎么办?如果一层层的无穷尽的 goroutine 呢?这就非常复杂了,即使我们定义很多 chan 也很难解决这个问题,因为 goroutine 的关系链就导致了这种场景非常复杂。

初识Context
上面说的这种场景是存在的,比如一个网络请求 Request,每个 Request 都需要开启一个 goroutine 做一些事情,这些 goroutine 又可能会开启其它的 goroutine。所以我们需要一种可以跟踪 goroutine 的方案,才可以达到控制它们的目的,这就是Go语言为我们提供的 Context,称之为上下文非常贴切,它就是 goroutine 的上下文。

利用context.Context重写上面示例:

func main() {
     
	ctx, cancel := context.WithCancel(context.Background())
	go func(ctx context.Context) {
     
		for {
     
			select {
     
			case <-ctx.Done():
				fmt.Println("监控退出,停止了...")
				return
			default:
				fmt.Println("goroutine监控中...")
				time.Sleep(2 * time.Second)
			}
		}
	}(ctx)

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")

	cancel()
	
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

Context 控制多个 goroutine

func main() {
     
	ctx, cancel := context.WithCancel(context.Background())
	go watch(ctx, "【监控1】")
	go watch(ctx, "【监控2】")
	go watch(ctx, "【监控3】")

	time.Sleep(10 * time.Second)
	fmt.Println("可以了,通知监控停止")
	cancel()
	//为了检测监控过是否停止,如果没有监控输出,就表示停止了
	time.Sleep(5 * time.Second)
}

func watch(ctx context.Context, name string) {
     
	for {
     
		select {
     
		case <-ctx.Done():
			fmt.Println(name, "监控退出,停止了...")
			return
		default:
			fmt.Println(name, "goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}

你可能感兴趣的:(golang并发编程,控制并发,sync.WaitGroup,Context)