Go语言核心36讲
郝林
《Go并发编程实战》作者,前轻松筹大数据负责人
立即订阅
24066 人已学习
课程目录
已完结 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讲
登录|注册

10 | 通道的基本操作

郝林 2018-09-03
作为 Go 语言最有特色的数据类型,通道(channel)完全可以与 goroutine(也可称为 go 程)并驾齐驱,共同代表 Go 语言独有的并发编程模式和编程哲学。
Don’t communicate by sharing memory; share memory by communicating. (不要通过共享内存来通信,而应该通过通信来共享内存。)
这是作为 Go 语言的主要创造者之一的 Rob Pike 的至理名言,这也充分体现了 Go 语言最重要的编程理念。而通道类型恰恰是后半句话的完美实现,我们可以利用通道在多个 goroutine 之间传递数据。

前导内容:通道的基础知识

通道类型的值本身就是并发安全的,这也是 Go 语言自带的、唯一一个可以满足并发安全性的类型。它使用起来十分简单,并不会徒增我们的心智负担。
在声明并初始化一个通道的时候,我们需要用到 Go 语言的内建函数make。就像用make初始化切片那样,我们传给这个函数的第一个参数应该是代表了通道的具体类型的类型字面量。
在声明一个通道类型变量的时候,我们首先要确定该通道类型的元素类型,这决定了我们可以通过这个通道传递什么类型的数据。
比如,类型字面量chan int,其中的chan是表示通道类型的关键字,而int则说明了该通道类型的元素类型。又比如,chan string代表了一个元素类型为string的通道类型。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Go语言核心36讲》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(49)

  • melon
    感觉chanel有点像socket的同步阻塞模式,只不过channel的发送端和接收端共享一个缓冲,套接字则是发送这边有发送缓冲,接收这边有接收缓冲,而且socket接收端如果先close的话,发送端再发送数据的也会引发panic(linux上会触发SIG_PIPE信号,不处理程序就崩溃了)。

    另使用demo21.go测试发送接收阻塞情况时需要额外空跑一个goroutine,否则会引发这样的panic(至 少1.11版是这样):fatal error: all goroutines are asleep - deadlock!

    作者回复: 对,所以注释中才会那么说。

    2018-09-03
    16
  • 忘怀
    Go里没有深copy。
    即便有的话这里可能也不会用吧,创建一个指针的内存开销绝大多数情况下要比重新开辟一块内存再把数据复制过来好的多吧。
    老师,这么说对吗?

    作者回复: 对,这就是传指针值的好处之一。

    2018-09-12
    13
  • 江山如画
    老师回复我后突然感觉不对劲,结构体是值类型,通道传输的时候会新拷贝一份对象,底层数据结构会被复制,引用类型可能就不一定了,又用数组和切片试了下,发现切片在通道传输的时候底层数据结构不会被复制,改了一个另外一个也会跟着改变,所以切片这里应该是浅复制,数组一个改了对另一个没有影响是深层复制,代码:

    //
    ch := make(chan []int, 1)
    s1 := []int{1, 2, 3}
    ch <- s1
    s2 := <-ch

    s2[0] = 100
    fmt.Println(s1, s2) //[100 2 3] [100 2 3]

    //
    ch2 := make(chan [3]int, 1)
    s3 := [3]int{1, 2, 3}
    ch2 <- s3
    s4 := <-ch2

    s3[0] = 100
    fmt.Println(s3, s4) //[100 2 3] [1 2 3]

    作者回复: 再说一遍,Go语言里没有深层复制。数组是值类型,所以会被完全复制。

    2018-09-03
    1
    12
  • 请叫我小岳同学
    1. 通道的长度,表示channel 缓冲的长度。当channel处于阻塞状态时,容纳最多的同类型的长度。
    2. 深拷贝

    作者回复: 第一个问题,长度代表通道当前包含的元素个数,容量就是初始化时你设置的那个数。

    第二个问题你再想想,可以做做试验。

    2018-09-03
    8
  • colonel
    通道底层存储数据的是链表还是数组?

    作者回复: 环形链表

    2018-09-23
    7
  • 有铭
    通道看上去像线程安全队列。那么这玩意在低层基于什么原理实现?cas?自旋?内核锁?性能如何
    2018-09-03
    5
  • 苏浅
    通道必须要手动关闭吗?go会自动清理吗?

    作者回复: 需要手动关闭,这是个很好的习惯,而且也可以利用关的动作来给接收方传递一个信号。Go的GC只会清理被分配到堆上的、不再有任何引用的对象。

    2018-09-03
    5
  • 来碗绿豆汤
    深copy还是浅copy,跟具体数据类型有关,引用型数据就是浅copy,数值型数据就是深copy.如,如果是切片类型则是浅copy,如果是数组则是深copy

    作者回复: 其实都是浅表复制。数组因为是值类型的,所以即使是浅复制也能完全复制过来。

    2018-09-03
    5
  • 张开广
    老师您好,通道这里我看了好几遍了,对于评论中有一个问题一直不明白,非常希望老师能够解答一下!

    同学阿拉丁的瓜的提问:
    请问老师,缓冲通道内的值是被并行读出的吗?
    比如两个goroutine分别为r1和r2;一个装满的容量为2的chan。
    当r1正在取出先入的数据时,r2是否可以取出后入的数据;还是说r2必须阻塞,等到先入数据被完全取走之后才能开始读取后入的数据?

    老师回答:
    作者回复: 可以同时进行,通道是并发安全的。但是不一定哪个g拿到哪个元素值。

    个人不明白,按照我看完的理解,同一个通道不管有多少并发在接收操作,同一个通道同时只能被一个goroutine操作,其他的都要在这个接收操作完成 “复制通道内的元素值”“放置副本到接收方”“删掉原值”三步完全完成后才可以继续进行的,负责就要一直阻塞才对

    老师原文中是这样的:
    类似的,在同一时刻,运行时系统也只会执行,对同一个通道的任意个接收操作中的某一个。
    直到这个元素值完全被移出该通道之后,其他针对该通道的接收操作才可能被执行。即使这些操作是并发执行的也是如此。
    这里所谓的并发执行,你可以这样认为,多个代码块分别在不同的 goroutine 之中,并有机会在同一个时间段内被执行。
    请老师解答一下我这个疑惑,非常感谢!

    作者回复: 你理解的没错,在同一时刻,只有一个goroutine能够对某一个通道进行取出操作,其他的试图对这个通道进行取出操作的goroutine都会被阻塞,并进入通道内部的队列排队。通道会保证这种操作是互斥的,并且是原子性的(完全取走一个元素值之后,下一个元素值才有可能被取)。

    我回答那位同学的意思是:两个go函数中的代码是有可能同时(在同一个很小的时间段内)执行到“取出操作”那一行代码的。不过我们完全不用在意,因为通道和运行时系统会保证这类操作的并发安全。

    可能我那个回答太短了吧,咱俩没有对上口径。

    2019-03-03
    4
  • 勇.Max
    老师,有个问题困惑很久,如果传指针的话,接收方和发送方不在一台机器上,指针还有效吗?(指针不是指向本地内存的吗)
    2018-11-15
    2
  • My dream
    老师,请教一下,通道的传值可以直接传指针不,你讲的拷贝,那么内存开销是很大的,如果通道传指针的话,会不会好很多

    作者回复: 原则上可以传任何类型的数据。不过,要是传指针的话要自己保证安全啊,原始数据放篡改之类的。

    2018-09-21
    2
  • 皮卡丘
    很明显现象是浅拷贝,为啥那么多说深拷贝的
    2018-09-03
    2
  • 左氧佛沙星人
    老师,我在做一道题,1个goroutine产生100个随机数放入一个channel,然后同时启动10个goroutine来读取,下面是我的代码,但是跑完了,fatal error: all goroutines are asleep - deadlock!直接提示死锁,能帮忙看下原因吗,老师

    main.main.func2(0xc0000a0000, 0xc00009e000)
    /Users/go/src/cliTest/exam/100IntChan.go:34 +0xc9
    created by main.main
    /Users/go/src/cliTest/exam/100IntChan.go:29 +0x110

    package main


    import (
    "math/rand"
    "log"
    )



    func main() {

    intChan := make(chan int,100)
    stateChan := make(chan struct{}, 10)
    sign := 0
    send := func() {
    for i:=0;i<100;i++ {
    intChan <- rand.Intn(100)
    }
    sign = 1
    }

    go send()
    for sign == 0 {

    }

    for j:=0;j<10;j++ {
    go func() {
    defer func() {
    stateChan <- struct{}{}
    }()
    for {
    elem, ok := <-intChan
    if ok {
    log.Print(elem,"\n")
    } else {
    break
    }
    }
    }()
    }
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan
    <- stateChan

    }

    作者回复: 问题很明显啊,怎么能用 for sign == 0 呢?两个问题,一个是你不能假定 go 函数(这里是 send 函数)的执行时机。另一个是你这样使用 sign 变量不是并发安全的,产生了竞态条件。用你的 stateChan 做状态传递多好。

    2019-06-11
    2
    1
  • Xiaolan🇨🇳
    通道的使用场景是不是同一个进程的不同线程间通讯使用?如果是不同程序进程还可以使用吗?

    作者回复: 它是面向同一个进程的。多个进程之间内存一般不会共享,所以没法用channel。进程间通讯可以考虑IPC方法,比如有名管道,你可以参看一下os.Pipe函数的文档。其实进程间通讯最强大和灵活的还是socket。

    2019-03-12
    1
  • 请问一下go语言可以实现并行运算么?我接触到现在看到的好像都是并发而非并行。
    2018-10-07
    1
  • My dream
    通道传值首先要保证原始数据的安全性是吗?所以一般不建议用传指针的方式来通讯,是不是这样理解的

    作者回复: 如果你的通道要给外人使用,或者通过通道对外提供功能,那就不要传指针值了,容易造成安全漏洞,另外这个时候最好限制下通道的方向。

    2018-09-27
    1
  • 阿拉丁的瓜灯儿
    请问老师,缓冲通道内的值是被并行读出的吗?
    比如两个goroutine分别为r1和r2;一个装满的容量为2的chan。
    当r1正在取出先入的数据时,r2是否可以取出后入的数据;还是说r2必须阻塞,等到先入数据被完全取走之后才能开始读取后入的数据?

    作者回复: 可以同时进行,通道是并发安全的。但是不一定哪个g拿到哪个元素值。

    2018-09-18
    1
  • 新垣结裤
    播音员的声音好磁性啊
    2018-09-11
    1
  • wh
    不要从接受端关闭channel算是基本原则了,另外如果有多个并发发送者,1个或多个接收者,有什么普适选择可以分享吗?

    作者回复: 可以用另外的标志位做,比如context。

    2018-09-05
    1
  • Yayu
    老师,我知道 golang 这门语言中所有的变量赋值操作都是 value copy的,不论这个变量是值类型,还是指针类型。关于您这里说的 shallow copy 与 deep copy 的问题我还是不是很清楚, google 了一下,每门语言的支持都不太一样,您是怎么定义这两个概念的?能否详细说一下?

    作者回复: 浅拷贝只是拷贝值以及值中直接包含的东西,深拷贝就是把所有深层次的结构一并拷贝。

    2018-09-05
    1
收起评论
49
返回
顶部