goroutine并发控制方式的总结

一、sync.WaitGroup{} 进行goroutine的控制

WaitGroup等待组是控制goroutine并发的一种方式,在我看来真正的意义是照顾弱势的“子goroutine”,通过Add()方法,确定预期要运行的goroutine的个数,然后goroutine内部通过defer延迟函数执行Done()方法,对一个goroutine运行结束后,进行计数减一,最后在“父goroutine”中的某个位置通过Wait()方法等待所有“子goroutine”执行结束,才能继续“父goroutine”的其他工作。

示例代码如下:

package main

import (
	"sync"
	"fmt"
)

func main() {

	var wg sync.WaitGroup

	fmt.Println("Prepare get in goroutine")

	wg.Add(1)
	go func() {
		defer wg.Done()
		fmt.Println("I`m in a goroutine.")
	}()

	fmt.Println("out the goroutine.")

	wg.Wait() // 等待main中的goroutine执行完,才能继续下去
	fmt.Println("main is stopping")
}

二、简陋地给goroutine的函数传channel值或通过作用域的channel变量进行控制

通过给函数传递一个通道参数,再通过select的其中一个case进行该通道接收判定,如果收到值就会执行终止goroutine的操作。
通过作用域的通道变量是指,该通道变量不是在goroutine中声明的,换句话说是在该goroutine外面对声明的这个通道变量进行传值操作,会对该goroutine中这个通道变量的case判定造成接收,进而可以执行终止goroutine的操作。

下面是通过goroutine的函数参数传值进行控制的方式。

package main

import (
	"fmt"
	"time"
)

func main() {

	ch := make(chan struct{})
	go printSomething(ch)

	time.Sleep(5*time.Second)
	close(ch)
	time.Sleep(10*time.Second)

}

func printSomething(ch chan struct{}) {
	ticker := time.NewTicker(3*time.Second)
	for {
		select {
		case <-ch:
			fmt.Println("finished printing work by channel")
			return
		case <-ticker.C:
			fmt.Println("finished printing work by time.Ticker")
			return
		default:
			time.Sleep(time.Second)
			fmt.Println("Hello World.")
		}
	}
}

下面的示例是在一个作用域下通过通道传值影响到goroutine内的这个等待传值的通道来控制goroutine的方式。

package main

import (
	"fmt"
	"time"
)

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

	go func() {
		for {
			select {
			case <-ch:
				fmt.Println("stop the goroutine...")
				return
			default:
				time.Sleep(time.Second)
				fmt.Println("Doria`s sheep is eating grass.")
			}
		}
	}()

	time.Sleep(5*time.Second)
	ch <- true

}

三、context进行goroutine的控制

context实现对goroutine的控制是通过该context的对象的取消函数对应的Done()函数,即一个struct{}类型的通道,当执行WithCancel()函数,会返回一个cancelFunc,当执行该函数时,会通知goroutine中的ctx.Done()函数,然后可以借此进行goroutine的停止退出。另外衍生出的方法还有WithDeadline(),WithTimeout(),WithValue(),其中WithDeadline(),WithTimeout()的功能大致与WithCancel()差不多,只是多了一个截止时间参数,而WithValue()是通过传入的key和val,在goroutine中传入实参context,然后共享到这个context的key的val值。

示例如下

package main

import (
	"context"
	"fmt"
	"time"
)
var lastName string = "lastName"

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	ctxHasValue := context.WithValue(ctx, lastName, "Stark")
	go sayHello(ctxHasValue, "Snow")
	go sayHello(ctxHasValue, "Joe")

	time.Sleep(5*time.Second)
	cancel()
	time.Sleep(5*time.Second)
	fmt.Println("main ends...")

}

func sayHello(ctx context.Context, name string) {
	for {
		select {
		case <-ctx.Done():
			fmt.Printf("%s says: Goodbye, Arya %s...\n", name, ctx.Value(lastName))
			return
		default:
			time.Sleep(time.Second)
			fmt.Printf("Hello, Arya %s, I`m %s\n", ctx.Value(lastName), name)
		}
	}
}

四、总结

简单总结一下,WaitGroup的作用是保证预期要执行的“子goroutine”都能执行到,而不被“父goroutine”欺负。而通过通道传值和context控制goroutine的停止,其实本质是没有太大区别的。但值得一提的是,前段时间在网上看过一篇文章,说Google公司的程序员写goroutine并发被要求并发函数的第一个参数需要是ctx context.Context,不论真假,至少说明了一件事,使用到context来控制goroutine是非常美观和方便的。在高并发的开发场景下,goroutine之间的通信和控制会变得异常艰难,如果使用到context,可以安全地停止goroutine,方便地进行通信操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值