Go底层原理与工程化实践
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1.1 Go语言并发编程入门

Go语言天然具备并发特性,基于go关键字就能很方便地创建一个可以并发执行的协程。什么场景下需要协程来并发执行呢?假设有这样一个服务或者接口:需要从其他三个服务获取数据(这三个服务之间没有互相依赖),处理后返回给客户端。

这时候如何处理呢?如果使用PHP语言开发,只能顺序调用这些服务获取数据;如果使用的是Java之类的多线程语言,为了提高接口性能,通常可能会开启多个线程并发获取数据。对于Go语言来说,我们可以开启多个协程去并发获取数据。Go语言实现这一需求的程序示例如下所示:

参考上面的代码,我们创建了3个子协程去并发执行任务。子协程模拟从第三方服务获取数据的功能,主协程需要等待3个子协程都执行结束,相当于等待其他3个服务返回数据。注意,这里使用sync.WaitGroup实现了等待功能,这需要我们在创建子协程之前调用一下函数wg.Add(标识开启了一个异步子协程),子协程结束之后调用一下函数wg.Done(标识一个异步子协程执行结束)。主协程调用函数wg.Wait之后将会一直阻塞,直到所有的异步子协程执行结束,主协程才能恢复执行。

这里需要再强调一点,main函数默认在主协程执行,而且一旦main函数执行结束,也意味着主协程执行结束,整个Go程序就会结束。所以如果删除了wg.Wait()这一行代码,你会发现子协程不一定能执行,因为子协程可能还没有被调度,Go程序就结束了。

还有一个问题,主协程如何获取子协程的返回数据呢?毕竟在我们的需求里,主协程是需要汇总处理3个子协程的返回数据的。想想最简单的方式,能不能通过在函数asyncWork中添加一个指针类型的输入参数作为返回值呢?当然可以。

其实也可以通过管道实现主协程和子协程之间的数据传递,Go语言管道的设计初衷就是实现协程间的数据传递。想象一下,管道就像一根水管,数据从管道的一端流入,从另外一端流出。基于管道改造一下上面的程序,如下所示:

参考上面的程序,变量datac是一个有缓冲管道,最多可以存储3个字符串。子协程用于向管道写入字符串,相当于子协程返回了数据。主协程循环3次,从管道读取数据并输出,相当于主协程接收到了子协程返回的数据。

Go语言基于协程和管道的并发编程非常常见。这里通过两个简单的程序来为大家做个演示,后续会详细介绍协程与管道的相关知识。