996的gopher

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数字全部打印出来吗?我们看一下执行结果:

执行结果(1)

大家可能会想这是为什么呢?其实很简单,就是当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)

}

实现方式很简单,只需要将匿名函式加上一个引数,然后将阵列的每个元素当作引数传进去就可以了,我们看一下执行结果:

执行结果(2)

这里我们可以看到阵列的所有元素都打印出来了。所以大家刚开始接触的时候一定要注意这个点。

我们现在来看一下这个主goroutine都做了哪些事情。当程式开始时先做一些系统的工作,然后主协程执行main函式,之后遇到go关键字,就会建立协程,当然不会立即建立,首先要设定每个协程能申请的栈空间的最大值,在32位系统中此最大值为250MB,64位的系统中此最大值为1GB,如果某个协程的栈空间超过此值,系统就会发起一个栈溢位的恐慌,即程式panic。

大致详细步骤如下:

程式开始时会在当前M(MPG模型中的M)检测系统的任务,之后主协程就会执行一些初始化的事情,检测当前M是不是runtime.m0,如果不是说明程式有问题,就会退出,然后会建立一个defer语句,目的是在主协程退出后清理现场。之后会启用后台清理内存垃圾的协程,并且用GC标识,最后在执行init函式。

在遇到go关键字时,就会建立或者复用协程来封装函式,这些协程会放入到相应的P的可执行G伫列中,之后就是排程器的呼叫过程了。

这些只是一个协程建立的一个简单过程,大家理解之后会对go的并发有更深的理解。

后续会有更多的模式和算法以及区块链相关的,如果你是想学习go语言或者是对设计模式或者算法感兴趣亦或是区块链开发工作者,都可以关注一下。(微信公众号:Go语言之美,更多go语言知识资讯等)。公众号会持续为大家分享更多干货。