Golang controls concurrency (sync.WaitGroup and context.Context)

We all know that concurrency is easy to implement in golang, and only one keyword gois needed . But after opening multiple goruntines, how do we manage them (including stopping and exiting goroutine, waiting for goruntine execution to complete, and continuing to let goruntine execute, etc.). We may encounter these in our daily business. Let's talk about how to manage goruntine correctly and gracefully in different scenarios, that is, control concurrency.

一,sync.WaitGroup

Role: Task scheduling, waiting for multiple goroutines to complete

Applicable scenario: When many goroutines do one thing in collaboration, because each goroutine is doing a part of this thing, only when all goroutines are completed, this thing is considered complete, this is the way of waiting.

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

This part is relatively simple, but not much description. See more about using sync.WaitGroup in asynchronous read and write

二,context.Context

The above scenario is to exit after waiting for multiple small tasks to be executed. Consider this scenario below: There is a continuous task, which is generally permanent and does not exit, such as a monitoring task. But sometimes due to special needs, it needs to withdraw after judging the conditions. Because there is no data connection (channel or the like) between this monitoring task and other tasks, it cannot be exited by sync.WaitGroup. Naturally, we can think of a medium to notify the monitoring task to exit. Global variables can serve as this medium, but due to poor security, they are not considered here (generally, global variables are not allowed in the code).

chan notice

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)
}

Output result:

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

This method is still relatively elegant, but it still has great limitations. What if we need to control a lot of goruntines to end? Do we have to create a lot of chan? Or what if more sub-goruntines are derived from sub-goruntine? What if there are endless layers of goroutine? This is very complicated. Even if we define a lot of chan, it is difficult to solve this problem, because the relationship chain of goroutine makes this scene very complicated.

When I first met Context
, the scenarios mentioned above exist. For example, for a network request, each Request needs to open a goroutine to do something, and these goroutines may open other goroutines. So we need a solution that can track goroutines in order to achieve the purpose of controlling them. This is the Context provided by the Go language. It is very appropriate to call it the context, which is the context of the goroutine.

Rewrite the above example using 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 controls multiple goroutines

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)
		}
	}
}

Guess you like

Origin blog.csdn.net/csdniter/article/details/109671025