10 | 通道的基本操作
该思维导图由 AI 生成,仅供参考
前导内容:通道的基础知识
- 深入了解
- 翻译
- 解释
- 总结
Go语言中的通道(channel)是一种独特的并发编程模式,与goroutine并驾齐驱,代表了Go语言独有的并发编程模式和编程哲学。通道类型的值本身是并发安全的,使用起来简单,不会增加心智负担。通过内建函数`make`声明并初始化通道,确定通道类型的元素类型,以及通道的容量。通道分为非缓冲通道和缓冲通道,具有不同的数据传递方式。发送和接收操作对元素值的处理是不可分割的,操作之间是互斥的,并且在完成之前会被阻塞。通道操作的阻塞实际上是为了实现操作的互斥和元素值的完整。通道的基本特性包括发送操作和接收操作之间的互斥性,对元素值的处理是不可分割的,以及操作在完成之前会被阻塞。通道的使用可以帮助实现并发编程,体现了Go语言的并发编程模式和编程哲学。 通道的长度代表着通道中当前存储的元素数量,而通道的容量则代表了通道可以存储的元素数量上限。元素值在经过通道传递时会被复制,这是浅表复制。发送操作和接收操作在特定情况下会被长时间的阻塞,如通道已满或已空时。同时,对于一个已初始化但未关闭的通道来说,收发操作一定不会引发panic,但一旦通道关闭,再对其进行发送操作会引发panic。因此,正确使用通道的操作规则和特性对于并发编程至关重要。 总的来说,通道是Go语言并发编程中的重要组成部分,具有独特的特性和行为模式,需要程序员深入理解并熟练运用。
《Go 语言核心 36 讲》,新⼈⾸单¥59
全部留言(77)
- 最新
- 精选
- 忘怀Go里没有深copy。 即便有的话这里可能也不会用吧,创建一个指针的内存开销绝大多数情况下要比重新开辟一块内存再把数据复制过来好的多吧。 老师,这么说对吗?
作者回复: 对,这就是传指针值的好处之一。
2018-09-1265 - colonel通道底层存储数据的是链表还是数组?
作者回复: 环形链表
2018-09-23248 - 江山如画老师回复我后突然感觉不对劲,结构体是值类型,通道传输的时候会新拷贝一份对象,底层数据结构会被复制,引用类型可能就不一定了,又用数组和切片试了下,发现切片在通道传输的时候底层数据结构不会被复制,改了一个另外一个也会跟着改变,所以切片这里应该是浅复制,数组一个改了对另一个没有影响是深层复制,代码: // 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-031336 - 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-0328 - 来碗绿豆汤深copy还是浅copy,跟具体数据类型有关,引用型数据就是浅copy,数值型数据就是深copy.如,如果是切片类型则是浅copy,如果是数组则是深copy
作者回复: 其实都是浅表复制。数组因为是值类型的,所以即使是浅复制也能完全复制过来。
2018-09-03220 - 会哭的鱼老师您好,通道这里我看了好几遍了,对于评论中有一个问题一直不明白,非常希望老师能够解答一下! 同学阿拉丁的瓜的提问: 请问老师,缓冲通道内的值是被并行读出的吗? 比如两个goroutine分别为r1和r2;一个装满的容量为2的chan。 当r1正在取出先入的数据时,r2是否可以取出后入的数据;还是说r2必须阻塞,等到先入数据被完全取走之后才能开始读取后入的数据? 老师回答: 作者回复: 可以同时进行,通道是并发安全的。但是不一定哪个g拿到哪个元素值。 个人不明白,按照我看完的理解,同一个通道不管有多少并发在接收操作,同一个通道同时只能被一个goroutine操作,其他的都要在这个接收操作完成 “复制通道内的元素值”“放置副本到接收方”“删掉原值”三步完全完成后才可以继续进行的,负责就要一直阻塞才对 老师原文中是这样的: 类似的,在同一时刻,运行时系统也只会执行,对同一个通道的任意个接收操作中的某一个。 直到这个元素值完全被移出该通道之后,其他针对该通道的接收操作才可能被执行。即使这些操作是并发执行的也是如此。 这里所谓的并发执行,你可以这样认为,多个代码块分别在不同的 goroutine 之中,并有机会在同一个时间段内被执行。 请老师解答一下我这个疑惑,非常感谢!
作者回复: 你理解的没错,在同一时刻,只有一个goroutine能够对某一个通道进行取出操作,其他的试图对这个通道进行取出操作的goroutine都会被阻塞,并进入通道内部的队列排队。通道会保证这种操作是互斥的,并且是原子性的(完全取走一个元素值之后,下一个元素值才有可能被取)。 我回答那位同学的意思是:两个go函数中的代码是有可能同时(在同一个很小的时间段内)执行到“取出操作”那一行代码的。不过我们完全不用在意,因为通道和运行时系统会保证这类操作的并发安全。 可能我那个回答太短了吧,咱俩没有对上口径。
2019-03-03515 - My dream老师,请教一下,通道的传值可以直接传指针不,你讲的拷贝,那么内存开销是很大的,如果通道传指针的话,会不会好很多
作者回复: 原则上可以传任何类型的数据。不过,要是传指针的话要自己保证安全啊,原始数据放篡改之类的。
2018-09-21213 - 请叫我小岳同学1. 通道的长度,表示channel 缓冲的长度。当channel处于阻塞状态时,容纳最多的同类型的长度。 2. 深拷贝
作者回复: 第一个问题,长度代表通道当前包含的元素个数,容量就是初始化时你设置的那个数。 第二个问题你再想想,可以做做试验。
2018-09-0311 - Geek_a8be59ch1 := make(chan int, 2) // 发送方。 go func() { for i := 0; i < 10; i++ { fmt.Printf("Sender: sending element %v...\n", i) ch1 <- i } fmt.Println("Sender: close the channel...") close(ch1) }() // 接收方。 for { elem, ok := <-ch1 if !ok { fmt.Println("Receiver: closed channel") break } fmt.Printf("Receiver: received an element: %v\n", elem) } fmt.Println("End.") 老师,根据您的提供的源码有三个问题需请教。 第一问题:第一次携程调度应该发生主携程中的elem, ok := <-ch1 这个代码处,这时候应该在chan有等待的协成,再第一向chan1<-i传值得时候,根据您的描述"当发送操作在执行的时候发现空的通道中,正好有等待的接收,那么会把元素直接复制给对方"。照这么说应该在这次就跳转到主协成中,并打印出接收到的数据了。但是实际是先发送i=3的时候才做第一次携程调度,请问这是为什么? 第二问题:缓存区的大小不是设置的是2么,为什么length当发送了i=3的时候才会阻塞发生调度呢,正常不是应该i=2的时候么 第三个问题:当for循环结束了以后 就是在chan关闭之前,为什么又能调度到主协成让他接收呢。不应该到这个协成调用结束么?
作者回复: 所以说不要用“协程”这个概念,因为“协程(coroutine)”指的是程序在同一个线程内的自行调度,是应用程序本身完全可控的。而 goroutine 的调度是 Go 语言的运行时系统发起的。 你不要揣测 Go 语言的调度器会怎样调度。你首先要知道哪些代码点是调度的时机(注意,到了调度时机也不一定发生调度,只是时机而已)。你还要知道如果想让多个 goroutine 按照你拟定的流程执行就需要用到 Channel 以及各种同步工具。 你说的“跳转到”只能在 coroutine 场景下才能这么说。在 goroutine 的场景下,没有“跳转”这么一说。 其一,你在上面的 for 语句中启用了一个 goroutine,你怎么就能断定后面的代码一定会先于这个 go 函数执行?不要做这种假设。因为连 goroutine 的调度都是并发的。 其二,两个 goroutine 一个 channel,一个 goroutine 发,一个 goroutine 取。这个 ch1 什么时候满、什么时候空,你基本上是确定不了的。因为两个 for 循环 在迭代的过程中都可能因被调用而换下 CPU。 其三,你要知道,几乎任何函数调用都存在调度时机,更何况是像 fmt.Println 这种需要 I/O 的重型操作。所以,为什么你那前一个 for 循环结束之后就不能被调度了呢? 以上是我通过你的文字表达猜测并回答的,并不一定完全匹配你要问的问题。还有问题的话再问我。 我觉得你对“并发”和“调度”这两个概念不清楚。我建议你好好看看专栏里讲 goroutine 的那几篇文章。有必要的话,买我的《Go 并发编程实战》第二版从头学一下。
2019-06-27310 - Yayu老师,我知道 golang 这门语言中所有的变量赋值操作都是 value copy的,不论这个变量是值类型,还是指针类型。关于您这里说的 shallow copy 与 deep copy 的问题我还是不是很清楚, google 了一下,每门语言的支持都不太一样,您是怎么定义这两个概念的?能否详细说一下?
作者回复: 浅拷贝只是拷贝值以及值中直接包含的东西,深拷贝就是把所有深层次的结构一并拷贝。
2018-09-0510