func ForGoStatement_1() {go func() {fmt.Println("go-func-1")}()
}func main() {ForGoStatement_1()
}

参考如下文章,会更清楚golang的协程:

【golang内幕之程序启动流程】【https://blog.csdn.net/QQ1130141391/article/details/96197570】

【golang内幕之协程状态切换】【https://blog.csdn.net/QQ1130141391/article/details/96350019】

golang的协程写法很简单:

go func()

func可以是匿名函数,也可以是命名函数。

golang编译器在编译时会将go func()转换程runtime中的newproc(func),这样就新创建了一个golang协程,此时新建的协程处于runnable状态并放置在p的队列中,并没有马上执行,而是等待协程调度器调度执行。

而main.main函数则是在主协程中执行,即main routine。当main.main函数返回时,main routine做一些简单的资源回收后,会调用exit(0)退出整个进程。

所以上面代码会不会输出go-func-1呢?

我只能说,从调度角度说,有可能,但微乎其微。

所以,现在运行很多次,基本都没任何输出,直接退出了进程。

Process finished with exit code 0

原因,新建的routine都还没机会调度,main routine就调用了exit(0)退出了进程。

那我们让main routine睡眠一下,让新建的routine有足够时间给调度:

func ForGoStatement_1() {go func() {fmt.Println("go-func-1")}()
}func main() {ForGoStatement_1()time.Sleep(1)
}
go-func-1Process finished with exit code 0
func ForGoStatement_2() {//loop 1for i := 0; i < 10; i++ {go func() {fmt.Println("i=", i)}()}//loop 2for j := 0; j < 10; j++ {go func(v int) {fmt.Println("j", v)}(j)}
}func main() {ForGoStatement_2()
}
"".ForGoStatement_2 STEXT size=242 args=0x0 locals=0x380x0000 00000 (for-go-statement.go:7) TEXT    "".ForGoStatement_2(SB), ABIInternal, $56-0...0x0028 00040 (for-go-statement.go:9)    LEAQ    type.int(SB), AX0x002f 00047 (for-go-statement.go:9)    PCDATA  $2, $00x002f 00047 (for-go-statement.go:9)  MOVQ    AX, (SP)0x0033 00051 (for-go-statement.go:9)    CALL    runtime.newobject(SB)0x0038 00056 (for-go-statement.go:9)   PCDATA  $2, $10x0038 00056 (for-go-statement.go:9)  MOVQ    8(SP), AX0x003d 00061 (for-go-statement.go:9)   PCDATA  $0, $10x003d 00061 (for-go-statement.go:9)  MOVQ    AX, "".&i+40(SP)0x0042 00066 (for-go-statement.go:9) PCDATA  $2, $00x0042 00066 (for-go-statement.go:9)  MOVQ    $0, (AX)0x0049 00073 (for-go-statement.go:9)    JMP 750x004b 00075 (for-go-statement.go:9)  PCDATA  $2, $10x004b 00075 (for-go-statement.go:9)  MOVQ    "".&i+40(SP), AX0x0050 00080 (for-go-statement.go:9) PCDATA  $2, $00x0050 00080 (for-go-statement.go:9)  CMPQ    (AX), $100x0054 00084 (for-go-statement.go:9)   JLT 880x0056 00086 (for-go-statement.go:9)  JMP 1500x0058 00088 (for-go-statement.go:10)    PCDATA  $2, $10x0058 00088 (for-go-statement.go:10) MOVQ    "".&i+40(SP), AX0x005d 00093 (for-go-statement.go:12)    MOVQ    AX, ""..autotmp_5+32(SP)0x0062 00098 (for-go-statement.go:10)    MOVL    $8, (SP)0x0069 00105 (for-go-statement.go:10)   PCDATA  $2, $20x0069 00105 (for-go-statement.go:10) LEAQ    "".ForGoStatement_2.func1·f(SB), CX0x0070 00112 (for-go-statement.go:10)  PCDATA  $2, $10x0070 00112 (for-go-statement.go:10) MOVQ    CX, 8(SP)0x0075 00117 (for-go-statement.go:10)  PCDATA  $2, $00x0075 00117 (for-go-statement.go:10) MOVQ    AX, 16(SP)0x007a 00122 (for-go-statement.go:10) CALL    runtime.newproc(SB)0x007f 00127 (for-go-statement.go:10)    JMP 1290x0081 00129 (for-go-statement.go:9) PCDATA  $2, $10x0081 00129 (for-go-statement.go:9)  MOVQ    "".&i+40(SP), AX0x0086 00134 (for-go-statement.go:9) PCDATA  $2, $00x0086 00134 (for-go-statement.go:9)  MOVQ    (AX), AX0x0089 00137 (for-go-statement.go:9)    PCDATA  $2, $30x0089 00137 (for-go-statement.go:9)  MOVQ    "".&i+40(SP), CX0x008e 00142 (for-go-statement.go:9) INCQ    AX0x0091 00145 (for-go-statement.go:9)  PCDATA  $2, $00x0091 00145 (for-go-statement.go:9)  MOVQ    AX, (CX)0x0094 00148 (for-go-statement.go:9)    JMP 750x0096 00150 (for-go-statement.go:16) PCDATA  $0, $00x0096 00150 (for-go-statement.go:16) MOVQ    $0, "".j+24(SP)0x009f 00159 (for-go-statement.go:16) JMP 1610x00a1 00161 (for-go-statement.go:16)    CMPQ    "".j+24(SP), $100x00a7 00167 (for-go-statement.go:16)    JLT 1710x00a9 00169 (for-go-statement.go:16)    JMP 2220x00ab 00171 (for-go-statement.go:17)    MOVL    $8, (SP)0x00b2 00178 (for-go-statement.go:17)   PCDATA  $2, $10x00b2 00178 (for-go-statement.go:17) LEAQ    "".ForGoStatement_2.func2·f(SB), AX0x00b9 00185 (for-go-statement.go:17)  PCDATA  $2, $00x00b9 00185 (for-go-statement.go:17) MOVQ    AX, 8(SP)0x00be 00190 (for-go-statement.go:17)  MOVQ    "".j+24(SP), CX0x00c3 00195 (for-go-statement.go:17) MOVQ    CX, 16(SP)0x00c8 00200 (for-go-statement.go:17) CALL    runtime.newproc(SB)0x00cd 00205 (for-go-statement.go:17)    JMP 2070x00cf 00207 (for-go-statement.go:16)    MOVQ    "".j+24(SP), AX0x00d4 00212 (for-go-statement.go:16) INCQ    AX0x00d7 00215 (for-go-statement.go:16) MOVQ    AX, "".j+24(SP)0x00dc 00220 (for-go-statement.go:16) JMP 1610x00de 00222 (<unknown line number>)   PCDATA  $2, $-20x00de 00222 (<unknown line number>)   PCDATA  $0, $-20x00de 00222 (<unknown line number>)   MOVQ    48(SP), BP0x00e3 00227 (<unknown line number>)    ADDQ    $56, SP0x00e7 00231 (<unknown line number>)   RET0x00e8 00232 (<unknown line number>)   NOP0x00e8 00232 (for-go-statement.go:7) PCDATA  $0, $-10x00e8 00232 (for-go-statement.go:7) PCDATA  $2, $-10x00e8 00232 (for-go-statement.go:7) CALL    runtime.morestack_noctxt(SB)0x00ed 00237 (for-go-statement.go:7)    JMP 0...

通过汇编,可以看出,loop 1中先创建了一个int类型的变量,然后作为newproc的参数传入,跟defer有点类似,但defer不会new一个object出来,而是直接取函数内变量地址值,而go func不同,有可能调用go func的调用函数已经结束了,但go func还会执行,因此需要开辟一个堆空间用于存放变量值。

而loop 2是直接使用j的值,没有取变量地址获取创建新的堆变量。

上面代码,会有输出吗?

有可能输出,也有可能不会输出。因为有可能main routine先退出了,新创建的routine没机会执行,但因为在main routine调用了多次的for循环,每次循环都会创建一个新routine,这本身就会消耗点时间,所以在main routine之前,已经创建好的routine还是有比较大的机会得到调度的。

上面代码,会输出全部吗?

有可能,但也有可能在main routine退出了进程,导致其他routine没机会执行,所以导致不会全部输出。

i= 3
i= 3
i= 10
i= 10
i= 10Process finished with exit code 0

Process finished with exit code 0
i= 3
i= 3
i= 10
i= 10
i= 10
j 2
i= 10
i= 10
j 0Process finished with exit code 0

以上是执行多次的结果,可以说输出是随机的,会不会输出,随机;会不会全部输出,随机(几率比较小);输出内容的顺序,随机。

如果我们想让全部输出后,才退出进程呢?

可以使用time.Sleep让main routine休眠,只要休眠时间足够长,是会输出全部内容的。

上面已经使用过这个套路了,换个新套路:

func ForGoStatement_3() {var wg sync.WaitGroupwg.Add(10 + 10)//loop 1for i := 0; i < 10; i++ {go func() {fmt.Println("i=", i)wg.Done()}()}//loop 2for j := 0; j < 10; j++ {go func(v int) {fmt.Println("j", v)wg.Done()}(j)}wg.Wait()
}func main() {ForGoStatement_3()
}

上面使用了WaitGroup同步原语,上面写法:

总共有10+10=20次机会,在20机会消耗完之前,WaitGroup会通过Wait等待,而通过Done就会消耗一次机会。

所以在20个新创建的routine都执行完且调用Done前,main routine都因为Wait阻塞着。

每个新创建的routine执行时都会输出一段内容,然后执行done,然后routine就执行完毕,即进入_Gdead状态,不会再执行,等待回收分配的资源了。

运行结果:

i= 3
i= 3
i= 10
i= 7
j 1
j 4
i= 8
j 7
j 3
j 9
i= 8
i= 10
j 0
j 6
j 5
i= 8
j 2
i= 6
j 8
i= 7Process finished with exit code 0

多次运行,都会输出20行内容然后才退出进程,WaitGroup达到了效果。

至于每次输出的20行内容还是随机的,因为20个新创建的routine都在随机执行。

问题:为什么新建的routine为什么会新执行?而不是新创建新执行?

因为我的机器CPU是多核的,在golang协程调度里面,多个核,意味这个创建多个m(系统线程)和多个p(golang对调度资源的一种抽象,每一个p维护一个存放待运行的routine的队列,新创建routine时,golang协程调度器会选择一个空闲的p,)并将新创建的routine放在p的本地队列中。

这意味着,我们新创建了20个routine,这20个routine可能分布在不同的p的队列中,也就有可能在不同的m(线程)中执行。

那如果只有一个m和一个p的情况,会怎么样呢?

func ForGoStatement_3() {runtime.GOMAXPROCS(1)var wg sync.WaitGroupwg.Add(10 + 10)//loop 1for i := 0; i < 10; i++ {go func() {fmt.Println("i=", i)wg.Done()}()}//loop 2for j := 0; j < 10; j++ {go func(v int) {fmt.Println("j", v)wg.Done()}(j)}wg.Wait()
}func main() {ForGoStatement_3()
}

通过runtime.GOMAXPROCS(1)设置了只有一个p,即只有一个routine队列,其实还有一个全局队列。

j 9
i= 10
i= 10
i= 10
i= 10
i= 10
i= 10
i= 10
i= 10
i= 10
i= 10
j 0
j 1
j 2
j 3
j 4
j 5
j 6
j 7
j 8Process finished with exit code 0

此时,多次运行,结果不是随机的。

事实上,我们不应依赖p数量和routine执行,我们开发过程中,应该认为每个routine的执行顺序的是随机的,不可预知的。

如果我们希望按照某种顺序执行,则应该使用chan或者锁来解决并发问题。

总结:p数量为1和>1的两种情况,在不考虑顺序问题(不应该也很难控制routine并发顺序),我们发现loop 1输出的值可以认为是随机的,而loop 2是按照我们传入的参数值。

loop 1可以简单理解为,引用传递,使用时使用引用指向的值。

loop 2可以简单理解为,值传递,使用时使用传进去的值。

golang内幕之for-go-statement相关推荐

  1. golang内幕之协程状态切换

    本文承接上一篇文章[golang内幕之程序启动流程][https://blog.csdn.net/QQ1130141391/article/details/96197570] 在 [golang内幕之 ...

  2. Golang 要注意的陷阱和常见错误

    原文: 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs 翻译: Go的50度灰:新Golang开发者要 ...

  3. golang 命令行_如何使用Golang编写快速有趣的命令行应用程序

    golang 命令行 by Peter Benjamin 彼得·本杰明(Peter Benjamin) 如何使用Golang编写快速有趣的命令行应用程序 (How to write fast, fun ...

  4. golang源码分析:编译过程词法解析的流程

    golang编译 由于golang作为静态语言,当使用go build时就会生成对应的编译完成之后的文件,那这个编译过程大致会做什么事情呢,在golang中的编译大致有哪些流程. golang示例代码 ...

  5. golang源码分析-启动过程概述

    golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...

  6. Golang之轻松化解defer的温柔陷阱

    什么是defer? defer是Go语言提供的一种用于注册延迟调用的机制:让函数或语句可以在当前函数执行完毕后(包括通过return正常结束或者panic导致的异常结束)执行. defer语句通常用于 ...

  7. golang 运算与循环

    一.golang运算符 1.算术运算符 + 相加 - 相减 * 相乘 / 相除 % 求余 ++ 自增 -- 自减 2.关系运算符 == 等于 != 不等于 > 大于 < 小于 >= ...

  8. golang 开发常见坑

    目录 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 不能使用短变量声明来设置字段值 Can't Use Shor ...

  9. golang 接口_「实战」助力数据库开发之接口篇 - Golang 连接 Greenplum

    Greenplum 作为一款强大的 HTAP 数据库,针对大多数流行语言都有相应的连接库.大部分均是与 PostgreSQL 采用相同的接口,但是也有部分接口是 Greenplum 专门优化后用于自身 ...

最新文章

  1. 飞利浦AC6608空气净化器粉尘传感器维修
  2. getattr的巨大作用
  3. win7中VS2010中安装CSS3.0问题解决方法
  4. PyCharm——turtle库的画布悬停解决方案
  5. 每小时50哈希——看看一个内部员工是如何摧毁整个公司网络的?
  6. java lambda循环_使用Java 8 Lambda简化嵌套循环
  7. axure 小程序 lib_小程序定制开发的步骤有哪些?
  8. apple组织名称是什么_什么是Apple Macintosh?
  9. php清空dns缓存文件,怎么清除DNS缓存
  10. 基础都掌握了却还是敲不出代码?编程新手如何快速提升coding能力?
  11. tar,jar,war的区别
  12. 高通平台开发系列讲解(USB篇)MBIM QXDM 日志解析
  13. Android 系统汉字转拼音 HanziToPinyin
  14. win10下载c语言软件下载,Win tc win10
  15. 计算机word中如何加入水印?
  16. 最强Verilog例化说明
  17. Matplotlib的一些常规操作
  18. 重载和重写的区别是什么
  19. 二层网络的未来?starkgate 带你体验二层桥接
  20. 超详细的纯CSS的照片墙特效

热门文章

  1. Appstore商店排名前十的威客应用!
  2. uniapp报错 -4048
  3. vue中的project和inject
  4. 【STDC】《Rethinking BiSeNet For Real-time Semantic Segmentation》
  5. ERROR in [copy-webpack-plugin] unable to locate ‘./src/lib/map‘ at ‘C:\Users\1\Desktop\node\lgx\src\
  6. 蘑菇街收购锐鲨科技,志在押注国货新浪潮?
  7. 看这一篇就够了:写简历、面试、谈薪酬的技巧和防坑指南
  8. 2020-09-24
  9. 网件交换机基本配置命令
  10. 机器学习coursera 第三章编程作业