GMP 调度模型,解释起来很简单,G ( goroutine ) 代表协程,M ( machine ) 代表线程, P(processor) 代表逻辑处理器。
1. Go 语言并发编程入门
Go 语言天然具备并发特性,基于 go 关键字就能很方便地创建一个可以并发执行的协程。什么场景下需要协程来并发执行呢?假设有这样一个服务或者接口:需要从其他三个服务获取数据(这三个服务之间没有互相依赖),处理后返回给客户端。
这时候如何处理呢?如果使用 PHP 语言开发,只能顺序调用这些服务获取数据;如果使用的是 Java 之类的多线程语言,为了提高接口性能,通常可能会开启多个线程并发获取数据。对于 Go 语言来说,我们可以开启多个协程去并发获取数据。Go 语言实现这一需求的程序示例如下所示:
Go">package mainimport ("fmt""sync"
)func asyncWork(workId int, wg *sync.WaitGroup) {// 开始异步任务wg.Add(1)go func() {fmt.Println(fmt.Sprintf("work %d exec", workId))//异步任务结束wg.Done()}()
}func main() {// WaitGroup 用于协程并发控制wg := sync.WaitGroup{}//启动 3 个协程并发执行任务for i := 0; i < 3; i++ {asyncWork(i, &wg)}//主协程等待任务结束wg.Wait()fmt.Println("work end")
}
程序运行结果如下所示:
Go">work 2 exec
work 1 exec
work 0 exec
work end
参考上面的代码,我们创建了 3 个子协程去并发执行任务。子协程模拟从第三方服务获取数据的功能,主协程需要等待 3 个子协程都执行结束,相当于等待其他 3 个服务返回数据。注意,这里使用 sync.WaitGroup 实现了等待功能,这需要我们在创建子协程之前调用一下函数 wg.Add(标识一个异步子协程执行结束)。主协程调用函数 wg.Wait 之后将会一直阻塞,直到所有的异步子协程执行结束,主协程才能恢复执行。
这里需要再强调一点,main 函数默认在主协程执行,而且一旦 main 函数执行结束,也意味着主协程执行结束,整个 Go 程序就会结束。所以如果删除了 wg.Wait() 这一行代码,你会发现子协程不一定能执行,因为子协程可能还没有被调度,Go 程序就结束了。
还有一个问题,主协程如何获取子协程的返回数据呢?毕竟在我们的需求里,主协程是需要汇总处理 3 个子协程的返回数据的。想想最简单的方式,能不能通过在函数 asyncWork 中添加一个指针类型的输入参数作为返回值呢?当然可以。
其实也可以通过管道实现主协程和子协程之间的数据传递,Go 语言管道的设计初衷就是实现协程间的数据传递。想象一下,管道就像一根水管,数据从管道的一端流入,从另外一端流出。基于管道改造一下上面的程序,如下所示:
Go">package mai