Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24139 人已学习
课程目录
已完结 54 讲
0/4登录后,你可以任选4讲全文学习。
开篇词+学习路线 (3讲)
开篇词 | 跟着学,你也能成为Go语言高手
免费
预习篇 | 写给0基础入门的Go语言学习者
50 | 学习专栏的正确姿势
模块一:Go语言基础知识 (6讲)
01 | 工作区和GOPATH
02 | 命令源码文件
03 | 库源码文件
04 | 程序实体的那些事儿(上)
05 | 程序实体的那些事儿(中)
06 | 程序实体的那些事儿 (下)
模块二:Go语言进阶技术 (16讲)
07 | 数组和切片
08 | container包中的那些容器
09 | 字典的操作和约束
10 | 通道的基本操作
11 | 通道的高级玩法
12 | 使用函数的正确姿势
13 | 结构体及其方法的使用法门
14 | 接口类型的合理运用
15 | 关于指针的有限操作
16 | go语句及其执行规则(上)
17 | go语句及其执行规则(下)
18 | if语句、for语句和switch语句
19 | 错误处理(上)
20 | 错误处理 (下)
21 | panic函数、recover函数以及defer语句 (上)
22 | panic函数、recover函数以及defer语句(下)
模块三:Go语言实战与应用 (27讲)
23 | 测试的基本规则和流程 (上)
24 | 测试的基本规则和流程(下)
25 | 更多的测试手法
26 | sync.Mutex与sync.RWMutex
27 | 条件变量sync.Cond (上)
28 | 条件变量sync.Cond (下)
29 | 原子操作(上)
30 | 原子操作(下)
31 | sync.WaitGroup和sync.Once
32 | context.Context类型
33 | 临时对象池sync.Pool
34 | 并发安全字典sync.Map (上)
35 | 并发安全字典sync.Map (下)
36 | unicode与字符编码
37 | strings包与字符串操作
38 | bytes包与字节串操作(上)
39 | bytes包与字节串操作(下)
40 | io包中的接口和工具 (上)
41 | io包中的接口和工具 (下)
42 | bufio包中的数据类型 (上)
43 | bufio包中的数据类型(下)
44 | 使用os包中的API (上)
45 | 使用os包中的API (下)
46 | 访问网络服务
47 | 基于HTTP协议的网络服务
48 | 程序性能分析基础(上)
49 | 程序性能分析基础(下)
尾声与思考题答案 (2讲)
尾声 | 愿你披荆斩棘,所向无敌
新年彩蛋 | 完整版思考题答案
Go语言核心36讲
登录|注册

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

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

前导内容:进程与线程

进程,描述的就是程序的执行过程,是运行着的程序的代表。换句话说,一个进程其实就是某个程序运行时的一个产物。如果说静静地躺在那里的代码就是程序的话,那么奔跑着的、正在发挥着既有功能的代码就可以被称为进程。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(30)

  • 云学
    请问这个例子中go routine对变量i的捕获是引用?
    2018-10-09
    1
    9
  • yandongxiao
    创建能创建成千上万个goroutine,但是不一定有那么多的系统资源,比如一般程序的最大可以打开4096个文件描述符。所以需要对goroutine 的启用数量加以限制。常用方法:
    1. buffered channel
    2. WaitGroup
    2018-09-27
    2
    7
  • cygnus
    除了用带缓冲的通道,还可以用runtime.GOMAXPROCS(maxProcs)来控制Goroutine并发数

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

    2018-09-17
    7
  • 孙稚昊
    如果是
    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
    1
    3
  • 哈噜噜
    请问,该示例代码中,当go函数是一个闭包?而传给fmt.Println(a ...interface{})的是变量i的引用吗?

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

    2019-03-01
    3
  • 🚲🏃🏊
    限制G的数量,可以使用goroutine pool
    2018-10-04
    3
  • colben
    "那也肯定是运气好而已"
    “总结”前的最后这句话说得太对了!
    2018-09-21
    2
  • Yayu
    不认为 buffered channel 的容量可以限制 goroutine 的数量,这个数量的最佳值应该跟硬件配置相关,但是如何限制应该是一个runtime 的参数来限制。具体是什么,还真不清楚。
    2018-09-17
    2
  • oyt
    要限制goroutine的启用数量,即达到规定限制数量后就阻塞。 可以使用有缓冲的channel,例如chanCount:=make(chan int,10) 可以限制启用10个goroutine。
    2018-09-17
    2
  • 坤坤
    既然 GPM 分层模型定义了 G 与 M 可以多对多,并且 G 的创建代价很小数量没有限制。为什么要对 goroutine 的启用数量加以限制?

    作者回复: 主要是因为计算机的内存(以及其他资源)总是有限的。从程序设计的角度讲,限制某种执行高并发任务的 goroutine 的数量也是很有必要的。另外,单进程内数十万的 goroutine 也会对 Go 语言的调度器和操作系统带来不小的压力。

    再有,我们应该尽量地去量化程序对资源的使用,并且有节制地区使用资源。当然,具体的使用上限设定成多少合适,还有以实际压测的结果为准。

    2019-10-03
    1
  • 开开心心
    这样子为什么会造成阻塞?我就加了 time.Sleep(1*time.Microsecond),而且时间很短
    for i := uint32(0); i < 10; i++ {
    go func(i uint32) {
    fn := func() {
    time.Sleep(1*time.Microsecond)
    fmt.Println(i)
    }
    //fmt.Println("准备",i)
    trigger(i, fn)
    }(i)
    }

    作者回复: 不应该,我这里也正常,不知道你还改了什么。

    2018-09-17
    1
  • x
    sync.WaitGroup来限制goroutine的数量? 每启动一个ADD一次,执行完再done一次,为了防止主线程的结束可以在循环后加上wait。

    作者回复: sync.WaitGroup 用在这里不是很好。

    2019-11-14
  • prader
    因为goroutine是异步并发执行,所以有可能什么打印不出来,也有可能,打印出的是乱序的0-9
    2019-11-02
  • ArtistLu
    老师请问下文中提到,这类队列中的 G 总是会按照先入先出的顺序……和Go 语言并不会去保证这些 goroutine 会以怎样的顺执行如何理解勒?

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

    2019-09-15
  • 这个题的例子肯定是没有任何显示,因为主线程都结束了,依托这个主线程的一切都灰飞烟灭了,但是要是后边加个定时,那会无序的显示0到9,因为每次for循环会把i的值拷贝到打印参数里。是一个副本不是原来的i
    2019-07-26
    1
  • 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
    1
  • 志鑫
    个人笔记:通过阻塞通道数组来实现

    package main
    import "fmt"

    func main() {
    const n = 10
    chs := [n + 1]chan struct{}{}
    chs[n] = make(chan struct{})
    for i := 0; i < n; i++ {
    chs[i] = make(chan struct{})
    go func(i int) {
    <-chs[i]
    fmt.Println(i)
    chs[i+1] <- struct{}{}
    }(i)
    }
    chs[0] <- struct{}{}
    <-chs[n]
    }
    2019-05-11
  • 王朝阳
    //看了评论 感觉这样的代码更有效说明问题
    package main
    import (
    "fmt"
    "time"
    )
    func main() {
    for i := 0; i < 100; i++ {
    go func(j int) {
    fmt.Printf("%d,%d\n", i, j)
    }(i)
    }
            // 尝试注释掉下边这行代码
    time.Sleep(time.Second * 2)
    }
    2019-05-08
  • 王朝阳
    package main

    import (
    "fmt"
    "time"
    )

    func main() {
    for i := 0; i < 10; i++ {
    go func() {
    fmt.Println(i)
    }()
    }
    time.Sleep(time.Second * 2)
    }

    运行结果 全部10,偶现4或者5

    作者回复: 希望在你看完了这一篇和下一篇之后能明白如此输出的原因。

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

    作者回复: 隐式传递?我好像没说过这个词吧。总之这种传递都是值传递。

    你说的这个问题与值传递没有直接关系。原因是go函数的执行顺序的不确定性。当这些go函数执行时,迭代变量i的值是什么,go函数就会拿到什么。你可以再看看我在文章中的说明。

    2019-03-31
收起评论
30
返回
顶部