go语言的创建goroutine很简单只需要一个关键字:go,在任何的函数前面使用go关键字就可以轻松的创建一个协程:
package main
import (
"fmt"
"time"
)
func main() {
t := []int{1, 2, 3, 4, 5, 6, 7, 8}
for _, v := range t {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second * 2)
}
这里我们用go关键字后面写了一个匿名函数,这样在 for循环 中就会创建多个协程,这里会创建8个,后面我们让主协程sleep了两秒钟,目的是等待其他协程结束后程序再退出。
但是上面的例子大家想一下结果是什么呢?将1-8数字全部打印出来吗?我们看一下运行结果:
大家可能会想这是为什么呢?其实很简单,就是当8个协程在打印 v 这个值的时候,v已经被赋值为8了,也就是说现在是当for循环执行结束后,这个8个协程才执行打印操作。那么上面的写法一定是这样吗?当然也不是,假如我们将t这个数组设置的多一些,例如20个或者30个元素,其中有一些协程是不一定打印数组t的最后一个元素的,如果大家不相信可以手动试一下,这里我不再展示运行结果,希望大家能够动起手,才能记得牢靠。
那么我们如何将数组t中的所有元素都打印出来呢?我写一下我认为的最好的解决办法:
package main import ( "fmt" "time" ) func main() { t := []int{1, 2, 3, 4, 5, 6, 7, 8} for _, v := range t { go func(value int) { fmt.Println(value) }(v) } time.Sleep(time.Second * 2) }
实现方式很简单,只需要将匿名函数加上一个参数,然后将数组的每个元素当作参数传进去就可以了,我们看一下运行结果:
这里我们可以看到数组的所有元素都打印出来了。所以大家刚开始接触的时候一定要注意这个点。
我们现在来看一下这个主goroutine都做了哪些事情。当程序开始时先做一些系统的工作,然后主协程执行main函数,之后遇到go关键字,就会创建协程,当然不会立即创建,首先要设定每个协程能申请的栈空间的最大值,在32位系统中此最大值为250MB,64位的系统中此最大值为1GB,如果某个协程的栈空间超过此值,系统就会发起一个栈溢出的恐慌,即程序panic。
大致详细步骤如下:
程序开始时会在当前M(MPG模型中的M)检测系统的任务,之后主协程就会执行一些初始化的事情,检测当前M是不是 runtime .m0,如果不是说明程序有问题,就会退出,然后会创建一个defer语句,目的是在主协程退出后清理现场。之后会启用后台清理内存垃圾的协程,并且用GC标识,最后在执行init函数。
在遇到go关键字时,就会创建或者复用协程来封装函数,这些协程会放入到相应的P的可运行G队列中,之后就是调度器的调用过程了。
这些只是一个协程创建的一个简单过程,大家理解之后会对go的并发有更深的理解。