Go语言的并发控制 Go语言原生支持的三种常用并发控制有Channel 、WaitGroup 、Context 等;
① Channel:使用chan控制子协程;
② WaitGroup:使用信号量机制控制子协程;
③ Context:使用上下文控制子协程;
三种方式各有优缺点:
Channel的优点是:实现简单,流程清晰易懂;
WaitGroup的优点是:子协程数可动态调整;
Context的优点是:对子协程派生出来的孙子协程的控制;
Channel使用chan控制子协程 chan是Go语言原生支持的通道类型、用于协程之间的通信;也可以用于并发控制;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "time" ) func main () { channels := make ([]chan int , 10 ) for i := 0 ; i < 10 ; i++ { channels[i] = make (chan int ) go Process(channels[i]) } for i, ch := range channels { <-ch fmt.Println("routine " , i, " quit!" ) } } func Process (ch chan int ) { time.Sleep(time.Second * 1 ) ch <- 1 }
Go语言生产者和消费者的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport "fmt" func consumer (data chan int , done chan bool ) { for v := range data { fmt.Println("recv:" , v) } done <- true } func producer (data chan int ) { for i := 0 ; i < 4 ; i++ { data <- i } close (data) } func main () { done := make (chan bool ) data := make (chan int ) go consumer(data, done) go producer(data) <-done }
使用Channel并发控制的总结:
使用Channel控制子协程的优点是:实现简单;
缺点是:当需要大量创建子协程时就需要有相同数量的channel,而且对于子协程继续派生出来的协程不方便控制;
WaitGroup使用信号量机制(等待组)控制子协程 WaitGroup是Go应用开发中经常用的并发控制技术;WaitGroup对外提供了三个接口:
① Add(delta int):将delta值加到counter中;
② Wait:阻塞等待信号量;
③ Done:counter递减1,按照waiter数值释放响应次数的信号量;
注意:
Add操作必须早于Wait(),否则会触发panic;
Add设置的值必须与实际等待的goroutine的个数一致,否则会触发panic;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "sync" "time" ) func main () { var wg sync.WaitGroup wg.Add(2 ) go func () { time.Sleep(time.Second) fmt.Println("finished!" ) wg.Done() }() go func () { time.Sleep(time.Second) wg.Done() }() wg.Wait() fmt.Println("ok" ) }
Context使用上下文控制子协程 Go语言的Context是开发中经常用到的并发控制技术;它与WaitGroup最大的不同点是:
context对于派生goroutine有更强的控制力,它可以控制多级的goroutine; context翻译成中文是”上下文”;
源码:src/context/context.go:Context定义了该接口:
1 2 3 4 5 6 7 8 9 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
一个典型的cancel context使用例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package mainimport ( "context" "fmt" "time" ) func main () { ctx, cancel := context.WithCancel(context.Background()) go HandelRequest(ctx) time.Sleep(time.Second * 5 ) fmt.Println("its time to stop all sub goroutine!" ) cancel() time.Sleep(time.Second * 5 ) } func HandelRequest (ctx context.Context) { go WriteRedis(ctx) go WriteDatabase(ctx) for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done." ) return default : fmt.Println("HandelRequest running." ) time.Sleep(time.Second * 2 ) } } } func WriteRedis (ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteRedis Done." ) return default : fmt.Println("WriteRedis running." ) time.Sleep(time.Second * 2 ) } } } func WriteDatabase (ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteDatabase Done." ) return default : fmt.Println("WriteDatabase running." ) time.Sleep(time.Second * 2 ) } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 WriteDatabase running. WriteRedis running. HandelRequest running. HandelRequest running. WriteRedis running. WriteDatabase running. WriteDatabase running. WriteRedis running. HandelRequest running. its time to stop all sub goroutine! HandelRequest Done. WriteRedis Done. WriteDatabase Done.