之前做项目的时候并没有系统学习golang,现在重新做分布式,读mit的raft源码的时候还是发现很多知识漏洞,所以准备系统学习下go语言,记录下难点。

在读源码中发现了很多有意思的东西,比如说

go func() {
// func body
}()

这样的定义,使用go关键字放在函数前,这样就定义了一个goroutine。

以下代码默认为10组,多组数据默认为50组:

看下面这段代码:

package main

import(
"fmt"
)

func foo() {
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
runtime.Gosched()
}

func main() {
foo();
}

打印结果:

理解golang中关键字-go_golang

不打印任何数字。

多组数据:

理解golang中关键字-go_golang_02

理解golang中关键字-go_golang_03

理解golang中关键字-go_i++_04

打印随机个。

这是因为go关键字定义了golang的并发编程goroutine

什么是goroutine?

goroutine是建立在线程上的轻量级的抽象,它允许我们以非常低的代价在同一个地址空间中并行的执行多个函数或者方法,相比于线程,它的创建和销毁代价小很多,并且它的调度室独立于线程的。在golang中使用go关键字创建一个goroutine。

上示代码中定义的foo函数中的print函数启用了并发编程,这个函数调用时是并发执行的,goroutine默认使用电脑的所有核心,其他核在进行go关键字后面的函数运算,但是由于程序执行完main函数结束后立刻结束,因此不确定其他核的函数有没有执行完毕,也就造成了每次运行打印的数字个数不一样。

至于为什么每次打印的数字都不一样呢,这是因为非单核处理并且内存逃逸,下一篇细讲下内存逃逸。

这一段,给出时间让多核运行完go func():

package main

import(
"fmt"
)

func foo() {
for i := 0; i < 10; i++ {
go func() {
fmt.Print(i, " ")
}()
}
runtime.Gosched()
time.Sleep(time.Second)
}

func main() {
foo();
}

打印结果 :

理解golang中关键字-go_i++_05

更多组的数据更明显:

理解golang中关键字-go_golang_06

给出1s时间后,就可以打印出所有数据了,但是由于多核存在,并不是打印的数据一样。

至于谁先执行谁后执行,如果任务优先级相同,则两个任务随机执行,没有明确的先后顺序。

我们设置单核:

package main

import(
"fmt"
)

func foo() {
runtime.GOMAXPROCS(1)
for i := 0; i < 10; i++ {
go func() {
fmt.Print(i, " ")
}()
}
runtime.Gosched()
time.Sleep(time.Second)
}

func main() {
foo();
}

打印结果:

理解golang中关键字-go_golang_07

多组数据:

理解golang中关键字-go_golang_08

这样子的话就是顺序执行了,为什么是10个10和50个50,就是单单因为内存逃逸了。

再看这个函数:

func bar() {
for i := 0; i < 10; i++ {
go fmt.Print(i, " ")
}
runtime.Gosched()
time.Sleep(time.Second)
}

这里没有使用go func()定义,而是直接go + 函数名,看看运行结果:

理解golang中关键字-go_i++_09

理解golang中关键字-go_i++_10

每次结果都不一样,这是因为多核原因,goroutine随机执行任务

设置单核:

func bar() {
runtime.GOMAXPROCS(1)
for i := 0; i < 10; i++ {
go fmt.Print(i, " ")
}
runtime.Gosched()
time.Sleep(time.Second)
}

运行结果:

理解golang中关键字-go_数据_11

是9 - 0...8并不是0...9,这是为什么呢?

其实,go 在把 goroutine 放入队列(go sched 内容会有另外的篇幅来说明)的时候还做了一件很特别的事:proc:4799 (next),代码内容如下:

if next {
retryNext:
oldnext := _p_.runnext
if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
goto retryNext
}
if oldnext == 0 {
return
}
// Kick the old runnext out to the regular run queue.
gp = oldnext.ptr()
}

这段代码的意思是 go 会把每个 P 所管理的最后一个 goroutine 放入 next 位置。为什么??

这是 go 设计认为或者是有过测试:如果一个 P 的 goroutine 队列在顺序执行的时候,因为 go sched 会有很多抢占或者调度。那

么从被执行的概率上来分析的话,放入一个 next 位置可使得每个 goroutine 的执行概率是相当的。

这个next的位置也就决定了9为什么最先打印。

参考:​​https://studygolang.com/articles/16469?fr=sidebar​