16 | go语句及其执行规则(上)
该思维导图由 AI 生成,仅供参考
前导内容:进程与线程
- 深入了解
- 翻译
- 解释
- 总结
Go语言中的`go`语句以及执行规则是本文的重点内容。文章首先回顾了进程与线程的概念,解释了操作系统提供的进程和线程以及Go语言中的goroutine的区别。接着详细介绍了Go语言运行时系统的调度器,以及G、P、M之间的关系,强调了Go语言在高并发情况下的稳定性和高效性。然后通过一个简单的编程题示例,说明了`go`语句的使用规则和可能出现的问题。文章还强调了主goroutine中的代码执行完毕后,Go程序会立即结束运行,可能导致未执行的goroutine中的代码不会被执行。总结来说,本文全面介绍了Go语言中的并发编程特点和`go`语句的使用,适合对Go语言并发编程感兴趣的读者阅读。文章内容深入浅出,对于理解并发编程模型和`go`语句的执行规则有很好的帮助。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(61)
- 最新
- 精选
- Kevin Xiao请问,该示例代码中,当go函数是一个闭包?而传给fmt.Println(a ...interface{})的是变量i的引用吗?
作者回复: Go语言里只有传值,没有传引用。如果go函数是无参数的匿名函数,那么在它里面的fmt.Println函数的参数只会在go函数被执行的时候才会求值。到那个时候,i的值可能已经是10(最后一个数)了,因为for语句那时候可能已经都执行完毕了。
2019-03-01226 - Geek_51aa7f郝老师您好,我想知道设置了P的最大数量为1之后,那么根据使用go语句提交的顺序,调度器可运行队列或者本地P的队列运行顺序是先入先出的,但下面代码返回的结果却先打印了9然后是顺序打印0-8,这是为什么呢 func main() { runtime.GOMAXPROCS(1) for i := 0; i < 10; i++ { go func(i int) { fmt.Println(i) }(i) } time.Sleep(time.Second) } // 9 0 1 2 3 4 5 6 7 8
作者回复: 即使P是1,M也可能有多个啊。“打印”是一种I/O事件。只要是I/O事件在执行的时候当前M和当前G就会脱离当前P,这时候当前P可以再去找别的G去运行。况且,操作系统也是会调度线程的运行的。 所以,这种顺序的预测还是不要做。如果想保证绝对的顺序,就要用同步工具或者通道。 最后,我看了一下最新的Go源码(go 1.14),在有一些时候调度器也会让G插队,比如在从通道的阻塞操作返回的时候,又比如在从网络I/O事件返回的时候,还比如在执行GC任务的时候。不过,这种插队行为也可能不成功,比如在本地P的可运行G队列已满的时候。 最后的最后,还是那句话,除非有同步工具或者通道的保障,否则不要去猜测G的执行顺序。
2020-06-05623 - ArtistLu老师请问下文中提到,这类队列中的 G 总是会按照先入先出的顺序……和Go 语言并不会去保证这些 goroutine 会以怎样的顺执行如何理解勒?
作者回复: 可运行 G 队列里面是先入先出的,可是调度器里有多个可运行 G 队列(每个 P 都有一个),而且哪个 G 什么进入哪个可运行G 队列还另有规则。所以这两句话并不矛盾。
2019-09-15222 - cygnus除了用带缓冲的通道,还可以用runtime.GOMAXPROCS(maxProcs)来控制Goroutine并发数
作者回复: 这是控制P的数量的。
2018-09-1718 - wilsongo func { } () 最后那个左右括号的作用是什么?
作者回复: 这个表示:调用前面的函数,也就是 func {}
2020-06-2516 - 孙稚昊如果是 package main import "fmt" func main() { for i := 0; i < 10; i++ { go func(i int) { fmt.Println(i) }(i) } } 这样子的话,i输入就是0-9了吧
作者回复: 我再强调一下。在go语句执行后,Go运行时系统会把对应的go函数装进新启用的goroutine中,随后调度执行。因为这个调度是不保证先后顺序的,所以这些go函数的执行在默认情况下也是乱序的。因此,你这样写无法保证数字的顺序打印。
2019-04-17512 - 坤坤既然 GPM 分层模型定义了 G 与 M 可以多对多,并且 G 的创建代价很小数量没有限制。为什么要对 goroutine 的启用数量加以限制?
作者回复: 主要是因为计算机的内存(以及其他资源)总是有限的。从程序设计的角度讲,限制某种执行高并发任务的 goroutine 的数量也是很有必要的。另外,单进程内数十万的 goroutine 也会对 Go 语言的调度器和操作系统带来不小的压力。 再有,我们应该尽量地去量化程序对资源的使用,并且有节制地区使用资源。当然,具体的使用上限设定成多少合适,还有以实际压测的结果为准。
2019-10-0310 - jacke问下: func main() { fmt.Println("cup ",runtime.NumCPU()) for i := 0; i < 10; i++ { go func() { fmt.Println(i, &i) }() } time.Sleep(time.Second) } 结果: cup 4 10 0xc420016088 10 0xc420016088 10 0xc420016088 10 0xc420016088 10 0xc420016088 10 0xc420016088 10 0xc420016088 4 0xc420016088 10 0xc420016088 10 0xc420016088 疑问:go routine的调度是随机,按照郝老师的讲解,在go routine得到执行的时候 fmt.Println,前面的i以及是10了,为什么后面还有打印是4的情况,而且看出来i地址一样,应该是同一个值?是不是go routine执行是并行的原因,所以打印到屏幕显示缓冲区,最后是乱序?
作者回复: 可以从两个方面理解: 1. 这些 go 函数的执行顺序是不固定的。这个想必你已经理解了。 2. 当一个 fmt.Println 引发 I/O 操作的时候也可能被中断(被切换下 CPU)。当它再次获得运行机会的时候,也许其他的 fmt.Println 都打印完了。这种切换其实就是 Go 运行时系统进行调度的结果。
2019-06-1922 - 杨康你好,老师,这个里面的变量i 传递到goroutine里面应该是值传递,为什么会出现1,10,10,10这种情况呢? 如果真的是值传递,怎么理解go语言中隐式传递是值传递这句话。
作者回复: 隐式传递?我好像没说过这个词吧。总之这种传递都是值传递。 你说的这个问题与值传递没有直接关系。原因是go函数的执行顺序的不确定性。当这些go函数执行时,迭代变量i的值是什么,go函数就会拿到什么。你可以再看看我在文章中的说明。
2019-03-312 - 帅demo38的例子,主gorutine结束之后,其他已经被调度的gorutine,会继续执行吗?
作者回复: 不会,主goroutine结束了进程也就结束了。
2019-01-242