协程(goroutine)
上一节我们介绍了并发与并行的区别,我们知道了Golang是一种并发编程语言。本节我们就来看下Golang是如何利用goroutine
,即协程
来实现并发的。
什么是goroutine(协程)
goroutine
指的是并发执行的函数/方法
,这里的函数/方法
我们可以理解为任务。goroutine
也可看作是轻量级的线程,创建goroutine
的开销要远远小于线程,在Golang中创建成千上万个goroutine
是常有的事情。
goroutine vs 线程
goroutine
开销更小。goroutine
仅占用几kb的内存开销,而且可以根据需要动态缩放,而线程占用的内存空间则更大,而且是固定不变的。goroutine
是在线程基础上的一层抽象封装,所有创建的的goroutine
本质上是复用少量的若干个线程。比如可能只有一个线程,成千上万个goroutine
都跑在这个线程上,分时复用。假设其中一个goroutine
阻塞了该线程的执行,比如在等待用户输出,那么Golang运行时会帮我们再创建一个系统级线程,将所有其他goroutine
切到新创建的线程上去。这些虽然听起来略显复杂,但是一般情况下我们无需关注这些底层的调度,Golang为我们提供好了方便易用的API,比如goroutine
来实现并发编程。goroutine
通过channel
,即管道
来实现goroutine
之间的通信。channel
能帮我们解决共享内存冲突的问题,这也是相对于线程的一个优势。我们下一节会详细介绍channel
。
开启一个goroutine
goroutine
开启一个goroutine
的方式很简单,只需要在调用的函数/方法
前加上关键字go
即可。
下面我们创建一个goroutine
:
这里go hello()
即开启了一个新的goroutine
,然后hello()
这个任务和会和main()
任务并发执行。main()
函数是默认goroutine
,我们一般称作主协程
。多运行该程序几次你会发现有可能输出三种不同的结果,分别是:
是不是有点惊讶?之所以会是这样的结果就是因为go hello()
创建的协程和主协程是并发执行的,谁都有可能先执行完。这就好比两个人赛跑,谁先跑到终点都不一定。我们修改下前面的程序:
我们通过time.Sleep(1 * time.Second)
来阻塞了主协程的执行,这时go hello()
开启的协程便在主协程结束之前有足够多的时间去执行。该程序先输出Hello world goroutine
,然后暂停了大约1秒钟,接着输出main function
。
这里我们使用time.Sleep
虽然能保证程序执行结果的一致性,但是真实应用中并没有这么用的,这里仅仅是为了演示子协程和主协程的同步问题。正规的做法是使用channel
,即管道
。这个后面一节再详细介绍。
开启多个goroutine
goroutine
下面我们再通过一个例子加深对goroutine
的理解,这次我们来开启多个goroutine
。
该程序创建了两个子协程,这两个子协程并发执行。numbers
协程每间隔250ms
打印一个数字;alphabets
协程每间隔400ms
打印一个字母。主协程等待3000ms
打印main terminated
然后退出。该程序执行结果如下,而且是稳定输出:
下图形象的说明了该程序的执行过程:
Last updated
Was this helpful?