Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
78704 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

16 | go语句及其执行规则(上)

你很棒,已经学完了关于 Go 语言数据类型的全部内容。我相信你不但已经知晓了怎样高效地使用 Go 语言内建的那些数据类型,还明白了怎样正确地创造自己的数据类型。
对于 Go 语言的编程知识,你确实已经知道了不少了。不过,如果你真想玩转 Go 语言还需要知道它的一些特色流程和语法。
尤其是我们将会在本篇文章中讨论的go语句,这也是 Go 语言的最大特色了。它足可以代表 Go 语言最重要的编程哲学和并发编程模式。
让我们再重温一下下面这句话:
Don’t communicate by sharing memory; share memory by communicating.
从 Go 语言编程的角度解释,这句话的意思就是:不要通过共享数据来通讯,恰恰相反,要以通讯的方式共享数据。
我们已经知道,通道(也就是 channel)类型的值,可以被用来以通讯的方式共享数据。更具体地说,它一般被用来在不同的 goroutine 之间传递数据。那么 goroutine 到底代表着什么呢?
简单来说,goroutine 代表着并发编程模型中的用户级线程。你可能已经知道,操作系统本身提供了进程和线程,这两种并发执行程序的工具。

前导内容:进程与线程

进程,描述的就是程序的执行过程,是运行着的程序的代表。换句话说,一个进程其实就是某个程序运行时的一个产物。如果说静静地躺在那里的代码就是程序的话,那么奔跑着的、正在发挥着既有功能的代码就可以被称为进程。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(60)

  • 最新
  • 精选
  • Kevin Xiao
    请问,该示例代码中,当go函数是一个闭包?而传给fmt.Println(a ...interface{})的是变量i的引用吗?

    作者回复: Go语言里只有传值,没有传引用。如果go函数是无参数的匿名函数,那么在它里面的fmt.Println函数的参数只会在go函数被执行的时候才会求值。到那个时候,i的值可能已经是10(最后一个数)了,因为for语句那时候可能已经都执行完毕了。

    2
    24
  • ArtistLu
    老师请问下文中提到,这类队列中的 G 总是会按照先入先出的顺序……和Go 语言并不会去保证这些 goroutine 会以怎样的顺执行如何理解勒?

    作者回复: 可运行 G 队列里面是先入先出的,可是调度器里有多个可运行 G 队列(每个 P 都有一个),而且哪个 G 什么进入哪个可运行G 队列还另有规则。所以这两句话并不矛盾。

    2
    21
  • 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的执行顺序。

    6
    20
  • cygnus
    除了用带缓冲的通道,还可以用runtime.GOMAXPROCS(maxProcs)来控制Goroutine并发数

    作者回复: 这是控制P的数量的。

    17
  • wilson
    go func { } () 最后那个左右括号的作用是什么?

    作者回复: 这个表示:调用前面的函数,也就是 func {}

    16
  • 孙稚昊
    如果是 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
    12
  • 坤坤
    既然 GPM 分层模型定义了 G 与 M 可以多对多,并且 G 的创建代价很小数量没有限制。为什么要对 goroutine 的启用数量加以限制?

    作者回复: 主要是因为计算机的内存(以及其他资源)总是有限的。从程序设计的角度讲,限制某种执行高并发任务的 goroutine 的数量也是很有必要的。另外,单进程内数十万的 goroutine 也会对 Go 语言的调度器和操作系统带来不小的压力。 再有,我们应该尽量地去量化程序对资源的使用,并且有节制地区使用资源。当然,具体的使用上限设定成多少合适,还有以实际压测的结果为准。

    10
  • 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 运行时系统进行调度的结果。

    2
    2
  • 杨康
    你好,老师,这个里面的变量i 传递到goroutine里面应该是值传递,为什么会出现1,10,10,10这种情况呢? 如果真的是值传递,怎么理解go语言中隐式传递是值传递这句话。

    作者回复: 隐式传递?我好像没说过这个词吧。总之这种传递都是值传递。 你说的这个问题与值传递没有直接关系。原因是go函数的执行顺序的不确定性。当这些go函数执行时,迭代变量i的值是什么,go函数就会拿到什么。你可以再看看我在文章中的说明。

    2
  • demo38的例子,主gorutine结束之后,其他已经被调度的gorutine,会继续执行吗?

    作者回复: 不会,主goroutine结束了进程也就结束了。

    2
收起评论
显示
设置
留言
60
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部