16 | go语句及其执行规则(上)
16 | go语句及其执行规则(上)
讲述:黄洲君
时长12:59大小7.43M
前导内容:进程与线程
问题解析
总结
赞 29
提建议
精选留言(59)
- Kevin Xiao2019-03-01请问,该示例代码中,当go函数是一个闭包?而传给fmt.Println(a ...interface{})的是变量i的引用吗?
作者回复: Go语言里只有传值,没有传引用。如果go函数是无参数的匿名函数,那么在它里面的fmt.Println函数的参数只会在go函数被执行的时候才会求值。到那个时候,i的值可能已经是10(最后一个数)了,因为for语句那时候可能已经都执行完毕了。
共 2 条评论22 - Geek_51aa7f2020-06-05郝老师您好,我想知道设置了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的执行顺序。
共 6 条评论19 - ArtistLu2019-09-15老师请问下文中提到,这类队列中的 G 总是会按照先入先出的顺序……和Go 语言并不会去保证这些 goroutine 会以怎样的顺执行如何理解勒?
作者回复: 可运行 G 队列里面是先入先出的,可是调度器里有多个可运行 G 队列(每个 P 都有一个),而且哪个 G 什么进入哪个可运行G 队列还另有规则。所以这两句话并不矛盾。
共 2 条评论19 - cygnus2018-09-17除了用带缓冲的通道,还可以用runtime.GOMAXPROCS(maxProcs)来控制Goroutine并发数
作者回复: 这是控制P的数量的。
16 - 云学2018-10-09请问这个例子中go routine对变量i的捕获是引用?共 2 条评论14
- wilson2020-06-25go func { } () 最后那个左右括号的作用是什么?
作者回复: 这个表示:调用前面的函数,也就是 func {}
14 - yandongxiao2018-09-27创建能创建成千上万个goroutine,但是不一定有那么多的系统资源,比如一般程序的最大可以打开4096个文件描述符。所以需要对goroutine 的启用数量加以限制。常用方法: 1. buffered channel 2. WaitGroup共 3 条评论12
- 孙稚昊2019-04-17如果是 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函数的执行在默认情况下也是乱序的。因此,你这样写无法保证数字的顺序打印。
共 5 条评论10 - 坤坤2019-10-03既然 GPM 分层模型定义了 G 与 M 可以多对多,并且 G 的创建代价很小数量没有限制。为什么要对 goroutine 的启用数量加以限制?
作者回复: 主要是因为计算机的内存(以及其他资源)总是有限的。从程序设计的角度讲,限制某种执行高并发任务的 goroutine 的数量也是很有必要的。另外,单进程内数十万的 goroutine 也会对 Go 语言的调度器和操作系统带来不小的压力。 再有,我们应该尽量地去量化程序对资源的使用,并且有节制地区使用资源。当然,具体的使用上限设定成多少合适,还有以实际压测的结果为准。
9 - 扩散性百万咸面包2020-04-11goroutine协程与线程进程的区别是什么(这是面试常考的)?为什么它的创建和销毁开销就这么低?再说了GPM也会对接系统级线程啊,也应该会存在系统调用,那优势体现在哪里呢? 还有能不能举几个关于用户级线程的例子?我知道系统级线程是用比如glibc的库函数创建的,但是不了解用户级线程。感觉多线程库,我们也确实只需要管线程的创建和销毁啊,并不管调度。展开5
- 🚲🏃🏊2018-10-04限制G的数量,可以使用goroutine pool4
- colben2018-09-21"那也肯定是运气好而已" “总结”前的最后这句话说得太对了!5
- oyt2018-09-17要限制goroutine的启用数量,即达到规定限制数量后就阻塞。 可以使用有缓冲的channel,例如chanCount:=make(chan int,10) 可以限制启用10个goroutine。3
- 杨康2019-03-31你好,老师,这个里面的变量i 传递到goroutine里面应该是值传递,为什么会出现1,10,10,10这种情况呢? 如果真的是值传递,怎么理解go语言中隐式传递是值传递这句话。
作者回复: 隐式传递?我好像没说过这个词吧。总之这种传递都是值传递。 你说的这个问题与值传递没有直接关系。原因是go函数的执行顺序的不确定性。当这些go函数执行时,迭代变量i的值是什么,go函数就会拿到什么。你可以再看看我在文章中的说明。
2 - 帅2019-01-24demo38的例子,主gorutine结束之后,其他已经被调度的gorutine,会继续执行吗?
作者回复: 不会,主goroutine结束了进程也就结束了。
2 - Yayu2018-09-17不认为 buffered channel 的容量可以限制 goroutine 的数量,这个数量的最佳值应该跟硬件配置相关,但是如何限制应该是一个runtime 的参数来限制。具体是什么,还真不清楚。2
- mkii2021-02-05试了一下让主goroutine退出前睡1秒,会打印多个10
作者回复: 这是由于:这些go函数的执行顺序在默认情况下相当于是伪随机的。
1 - 水先生2020-01-03func main() { for i := 0; i < 10; i++ { fmt.Println(i) // the first Print go func() { fmt.Println(i) // the second Print }() } } ------- 第一个print算是为go函数争取了执行时间吗?得到的结果是0至9顺序,然后3,6,10,10,10。 按理,循环到10,go函数不是应该结束了么?为啥还会多两个10的呀? 麻烦老师~展开
作者回复: 这里的两个print是做对比用的吧。0~9顺序打印应该是第一个print打印出来的。你可以把两个print打印的东西分别加上不同的前缀。这样就容易区分了。 go函数的执行会稍微滞后一些,所以当 for 语句执行完的时候(迭代变量 i 会定格在 10 这个值上),有的go函数可能还没开始执行。等到它们执行的时候,打印变量 i,就只会打印出 10。
1 - lixiaofeng2019-12-24goroutine 代表着并发模型中的用户级线程。 一个进程就是某个程序运行时的产物。 线程总是在进程之内,它可以被视为进程中运行着的控制流。 每个进程的第一个线程都会随着该进程的启动而创建,他们可以被称为所属进程的主线程。 主线程之外的其他线程都由代码显示的创建和销毁。 Go语言运行时(runtime)系统会帮助我们自动创建和销毁系统级线程。 Go 语言不但有独特的并发编程模型,以及用户级线程goroutine ,还拥有强大的goroutine 。 调度器是Go 语言运行时(runtime)的重要组成部分,它主要负责统筹调配go 并发编程模型中的三个主要元素: Goroutine processor, mechine,系统级线程 G和M 之间,由于P的存在呈现出,多对多的关系 当一个G由于某些原因暂停时,P 会及时发现,并把相应的M 分开,给其他的G 使用。展开1
- jacke2019-06-19问下: 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 运行时系统进行调度的结果。
共 2 条评论2