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

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

扩展知识:对goroutine的启用数量加以限制
解释程序运行结果的重要原理
理解运行时系统和调度器的工作
理解goroutine的地位和作用
打下坚实的基础
不确定性:goroutine的执行顺序不可预知
主goroutine中的代码执行完毕后,程序结束运行
go函数的执行时间滞后于go语句的执行时间
go语句执行后立即执行后续语句
P可以承载多个G,并与M进行对接
G代表goroutine,M代表系统级线程
负责调配G、P和M
劣势:复杂
优势:速度快、灵活
用户级线程由程序自己控制
Go程序中,运行时系统会自动创建和销毁系统级线程
进程至少包含一个线程,多个线程可以并发执行
线程是进程内部的控制流
电脑和手机同时运行多个应用程序的原因
进程描述程序的执行过程
下一篇内容
总结
主题:go语句的执行规则
Go语言的调度器
用户级线程与系统级线程
进程与线程
goroutine代表并发编程模型中的用户级线程
通道(channel)类型的值用于在不同的goroutine之间传递数据
不要通过共享数据来通讯,要以通讯的方式共享数据
go语句代表Go语言的最大特色
特色流程和语法
学完Go语言数据类型
go语句以及执行规则(上)
参考文章

该思维导图由 AI 生成,仅供参考

你很棒,已经学完了关于 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语言中的`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-01
    2
    26
  • 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-05
    6
    23
  • ArtistLu
    老师请问下文中提到,这类队列中的 G 总是会按照先入先出的顺序……和Go 语言并不会去保证这些 goroutine 会以怎样的顺执行如何理解勒?

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

    2019-09-15
    2
    22
  • cygnus
    除了用带缓冲的通道,还可以用runtime.GOMAXPROCS(maxProcs)来控制Goroutine并发数

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

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

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

    2020-06-25
    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函数的执行在默认情况下也是乱序的。因此,你这样写无法保证数字的顺序打印。

    2019-04-17
    5
    12
  • 坤坤
    既然 GPM 分层模型定义了 G 与 M 可以多对多,并且 G 的创建代价很小数量没有限制。为什么要对 goroutine 的启用数量加以限制?

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

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

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

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

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

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

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