深入浅出Golang关键字"go"

栏目: Go · 发布时间: 5年前

内容简介:昨天,有人拿着下面这段代码,问题我结果为什么不是10个10?当然,这道题的结果,我相信很大一部分人都会弄错。所以,我希望做错的人都能静下心来看完本人的分析!

1. 写在前面

昨天,有人拿着下面这段代码,问题我结果为什么不是10个10?

当然,这道题的结果,我相信很大一部分人都会弄错。

所以,我希望做错的人都能静下心来看完本人的分析! 运行看结果

func main() {
    runtime.GOMAXPROCS(1)
    for i := 0; i < 10; i++ {
        go println(i)
    }
    runtime.Gosched()
    time.Sleep(time.Second)
}

2. 从源码入手

此例中 “runtime” 的问题暂且不详说,后面会再来分析,但是,必需要明白的是 runtime. GOMAXPROCS(1) 强行指定了只创建一个 “P” 来处理并发,这使得例子中的 10 个 goroutine 会是串行的。接下来,我们就来一点一点剖析关键字 “go”!

2.1 知其然,知其所以然

问我问题的人,其实也知道要小心的处理 for 循环中变量,但是他却不了解哪种情况下的变量才是要真正小心行事的。我相信还有很多人会把例子和下面的代码理解混了: 运行看结果

func main() {
    runtime.GOMAXPROCS(1)
    for i := 0; i < 10; i++ {
        go func() {
            println(i)
        }()
    }
    runtime.Gosched()
    time.Sleep(time.Second)
}

结果显示:10个10

仔细对比上述的两段代码,会发现区别仅仅是 “go” 关键字后的函数有区别而已。那么这一点小区别,为什么会带来这么大的区别呢?我们都知道 “go” 是创建了 goroutine ,但是对于计算机而言,goroutine 只是语言封装的语法糖而已, 对于计算机依旧是识别指令及内存里的值而已 。那么 goroutine 在被创建后,留给计算机是什么样的内存布局(数据结构)呢?

runtime2.go:338 ( type g struct ) 定义,打开文件后,你会看到有很多的信息,但是,对于我们当前需要理解的是:编译器会把 go 后面的方法和参数打包在 goroutine 里,在上述例子中,关注如下参数:

type g struct {
    ...
    FuncVal*    fnstart;        // goroutine运行的函数
    void*    param;        // 用于传递参数,睡眠时其它goroutine设置param,唤醒时此goroutine可以获取
    ...
}

也就是说,运行到 go 的时候,编译器就已经把 goroutine 需要运行的参数与方法都保存了,对于 demo 1 来说就是保存了 { println, current_i },而 demo 2 保存的是 { main.func_xxx, nil },这里读者可能会注意到,第二个并没有把 i 传入到匿名函数中,但是引用的时候并没有发生 panic 。为什么?因为这里有内存逃逸,这也是为什么 demo 2 会输出 10 个 10 的原因,本次不展开细说。只需要记住一点: 编译器会把 go 后面跟着的参数与函数都打包成了对象,等待系统调度。

2.2 为什么??

本文说到这,其实只说了小部分,并没有说为什么 demo 1 的结果会与大家想象的差很多。虽说 GOMAXPROCS 为 1 导致所有的 goroutine 变成了串行。可是结果也不是大家想象的 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 位置也就解释了 demo 1 的结果为什么会是 9 --- 0~8 。本文到这已经说明了前面的问题,但是,demo 中出现了

runtime.Gosched()

这一行代码。

如果注释掉结果会怎样??

如果把这一行换成 runtime.Goexit() 或者 os.Exit(0) 又会是如何呢??

欢迎大家尝试并可和我讨论!!!

大家可关注公众号 cn_isnap 并留言!!


以上所述就是小编给大家介绍的《深入浅出Golang关键字"go"》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Essential PHP Security

Essential PHP Security

Chris Shiflett / O'Reilly Media / 2005-10-13 / USD 29.95

Being highly flexible in building dynamic, database-driven web applications makes the PHP programming language one of the most popular web development tools in use today. It also works beautifully wit......一起来看看 《Essential PHP Security》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具