Go 并发机制:Goroutine
Go 是当前一门热门的编程语言,其优秀的并发特性吸引了无数程序员的目光。
Go 的并发特性是一个比较大的话题,笔者计划从以下三个方面讨论:
- Go goroutine
- Go channel
- Go select
本文讨论 Go 的 goroutine 并发机制。
并发与并行
在讨论 goroutine 之前,我们先来看下并发与并行的区别。
多线程程序在单核心的 cpu 上运行,称为并发;多线程程序在多核心的 cpu 上运行,称为并行。并发与并行并不相同,并发主要由切换时间片来实现“同时”运行,并行则是直接利用多核实现多线程的运行。
以生活中慢跑例子来说明并发与并行的区别。
一个人在进行慢跑,途中鞋带松了。这时,他停止跑步,系好鞋带,然后再继续跑步。这是并发的经典示例,即这个人能够处理跑步和系鞋带,可以理解为这个人可以“同时”(at onece)处理多个事情。
同样以慢跑来例子来说明并行的含义。假设一个人正在戴着 airpods 听音乐慢跑。在这种情况下,这个人同时(at the same time)进行慢跑和听音乐,这就是所谓的并行。
Go 使用 goroutine 和 channel 实现并发特性。
什么是 Goroutine
Goroutine 是一个轻量级的可独立运行的工作单元,可以看作是轻量级的线程。与线程相比,创建 goroutine 的成本很小,因此 go 应用程序通常会同时运行数千个 goroutine。
Goroutine 的优势
- 创建 goroutine 的成本远小于线程。Goroutine 的栈大小只有几 KB,且可以根据应用程的需要增长和收缩。而对于线程,栈大小必须指定和固定下来
- 多个 goroutine 可以复用一个操作系统的线程。在一个有数千个 goroutine 的程序中可能只有一个线程。如果一个线程由于 goroutine 等待用户输入而阻塞,则会创建一个新的线程,其余的 goroutine 也会移动到这个新的线程运行。这些对于程序员来说都是透明的
- Goroutine 使用通道(channel)来通信。通道的使用可以防止访问共享内存时出现资源竞争的问题。有关通道的机制,我们在下一节进行描述
如何启动一个 Goroutine
为了启动一个 goroutine,只需要在函数或方法前面添加上 go 关键字,这样一来,我们就启动了一个 goroutine ,这个 goroutine 会并发地运行。
1 | package main |
上面的源程序中,我们使用 go hello() 启动一个 goroutine,这样 hello() 函数就会在一个 goroutine 中运行。同时, main() 函数会在另一个被称为 main goroutine 的 goroutine 中运行,hello() 函数与 main() 函数实现了并发运行。
运行程序,得到输出:
1 | main function |
输出结果与我们预期不一致,我们本以为会同时输出 Hello world goroutine 和 main function,但实际上只输出main function。为什么会这样呢?
原因如下。
- 当启动一个新的 goroutine 后会立即返回。跟普通的函数或方法不同,主函数并不会等待新启动的 goroutine 返回,而是会继续执行下一行主函数的语句
- 当主函数所在的 main goroutine 执行结束后,程序也运行结束,这样其他的 goroutine 也不再执行
由上面的分析可知,当主函数执行完 go hello() 后,会马上执行下一行打印语句,然后程序结束了运行。这样 hello goroutine 也就没有机会执行。
为解决hello goroutine 未执行的问题,可以在主函数中使用 Sleep 语句:
1 | package main |
在上面的程序中,我们使用了语句 time.Sleep(1 * time.Second) ,这样主函数会等待 1 秒钟,在这 1 秒内,hello goroutine 会输出 Hello world goroutine ,然后主函数会再输出 main function。
上面的程序仅作示例使用,实际中,为了让 main goroutine 等待其他的 goroutine 执行完毕,可以使用 channel(通道)。我们将在下一篇文章中对 channel 进行描述。
启动多个 Goroutine
接下来我们编写一个更复杂的程序,在这个程序中,会同时启动多个 goroutine。
1 | package main |
在上面的程序中,分别启动了 numbers 和 alphabets 两个 goroutine,这个两个 goroutine 会并发执行。
numbers goroutine 会睡眠 250 毫秒,然后打印数字 1,接着睡眠 250 毫秒,然后打印数字 2,直至打印 数字5。
同样的,alphabets goroutine 睡眠 400 毫秒,然后打印 字母 a,直到打印字母 e。
main goroutine 启动两个 goroutine 后,睡眠 3000 毫秒,然后结束运行。
运行程序,得到输出:
1 a 2 3 b 4 c 5 d e main terminated
可以使用下图来表示几个 goroutine 执行情况。

第一个蓝色的长条图表示 numbers goroutine 的执行情况,每隔 250 毫秒打印一个数字。第二个红色的长条图表示alphabets goroutine 的执行情况,每隔 400 毫秒打印一个字母。第三个绿色的长条图表示 main goroutine 的执行情况,睡眠 3000 毫秒,然后结束执行。最后一个长条图表示程序的实际输出。通过这个图,可以清晰得到程序的执行和输出结果。