Golang中关于channel的问题探讨
Golang中关于channel的问题探讨 你好!
我正在持续学习Go语言,目前正处在“并发与通道”这一章节。我有一个包含4个[]int的数组。针对这4个[]int中的每一个,我都会进行一些操作,并将结果传递给一个通道。然后,我将结果合并到一个数组中,该数组包含了这4个goroutine的结果。
代码如下:
result := []int{}
ch := make(chan []int)
[...] // 加载切片
for i := 0; i < numberArray; i++ {
go func(sli []int, i int, ch chan<- []int) {
sort.Sort(sort.IntSlice(sli))
fmt.Println("sorted subarray #", i, sli)
ch <- sli
}(slices[i], i, ch)
}
for i := 0; i < numberArray; i++ {
result = append(result, <-ch...)
}
问题:
如果这些goroutine是并发执行的,那么我最后是如何能够将每个goroutine的结果合并到一个“result”数组中的呢?我很难理解数据是如何传输到通道的。最后一个代码块是在所有goroutine都完成后才执行的吗?
为什么我不需要使用make(chan []int, 4)来创建通道?我以为需要一个长度正好等于goroutine数量的通道,以便所有结果都能被正确接收。
谢谢!
更多关于Golang中关于channel的问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢你清晰的回答,Yamil! 使用 Go 协程并不容易,因为我很难真正“想象”出工作流程的图景 🙂
第 8 / 9 行会在第 2 行之前执行。
更多关于Golang中关于channel的问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你好,Yamil!
感谢你的回答。
第一个goroutine是在它首次到达第9行时执行,还是在它执行完第9行之后才执行?
抱歉,这对我来说有点难理解。
当执行 <- ch 时,当前线程被阻塞,允许 goroutine 执行,然后在 goroutine 中,ch <- sli 会阻塞该 goroutine 并将执行权交还给主线程,直到 <- ch 完成并从通道中读取到值。
在第(2)行只是定义了goroutine,它会在主线程被阻塞时被调用。 这里有一个很好的教程 https://golangbot.com/goroutines/(以及Go语言中的其他结构) 是的,第一次理解起来确实有点困难……
(1) for i := 0; i < numberArray; i++ {
(2) go func(sli []int, i int, ch chan<- []int) {
(3) sort.Sort(sort.IntSlice(sli))
(4) fmt.Println("sorted subarray #", i, sli)
(5) ch <- sli
(6) }(slices[i], i, ch)
(7)}
(8)for i := 0; i < numberArray; i++ {
(9) result = append(result, <-ch...)
(10)}
- 当执行到第 (9) 行时,通道读取操作会阻塞当前线程的执行,并触发 goroutine 的执行。
- goroutine 执行,然后到达第 (5) 行,控制权返回给主线程(第 9 行),并从通道读取值,将其添加到切片中。
- 进入下一次迭代 (8),到达第 (9) 行,整个过程重新开始。
在Go语言中,通道(channel)是并发安全的,可以安全地在多个goroutine之间传递数据。让我们分析你的代码:
通道工作原理
你的代码正确使用了无缓冲通道(make(chan []int))。无缓冲通道的特点是:
- 发送操作会阻塞,直到有接收方准备好接收
- 接收操作会阻塞,直到有发送方准备好发送
代码执行流程
// 示例代码说明
package main
import (
"fmt"
"sort"
)
func main() {
numberArray := 4
slices := [][]int{
{3, 1, 4},
{9, 2, 7},
{5, 8, 6},
{0, 10, 11},
}
result := []int{}
ch := make(chan []int) // 无缓冲通道
// 启动4个goroutine并发排序
for i := 0; i < numberArray; i++ {
go func(sli []int, i int, ch chan<- []int) {
sort.Sort(sort.IntSlice(sli))
fmt.Printf("sorted subarray #%d: %v\n", i, sli)
ch <- sli // 发送排序后的切片到通道
}(slices[i], i, ch)
}
// 接收所有结果
for i := 0; i < numberArray; i++ {
// <-ch 会阻塞,直到有goroutine发送数据
// append(result, <-ch...) 将接收到的切片展开并追加到result
result = append(result, <-ch...)
}
fmt.Println("Final merged result:", result)
}
回答你的问题
1. 数据如何传输到通道?
- 每个goroutine执行
ch <- sli时,会将排序后的切片发送到通道 - 主goroutine中的
<-ch会从通道接收这些切片 append(result, <-ch...)中的...操作符将接收到的切片展开为多个int元素
2. 最后一个代码块何时执行?
for i := 0; i < numberArray; i++ {
result = append(result, <-ch...)
}
这个循环会立即开始执行,不会等待所有goroutine完成。具体流程:
- 循环开始,执行
<-ch(阻塞等待) - 当第一个goroutine发送数据时,接收第一个结果
- 继续循环,再次执行
<-ch(阻塞等待下一个结果) - 重复直到接收完4个结果
3. 为什么不需要缓冲通道?
无缓冲通道(make(chan []int))在这里完全够用,因为:
- 发送和接收是同步的
- 主goroutine在循环中等待接收,每个goroutine发送后会被阻塞直到主goroutine接收
- 不需要预分配缓冲区,因为发送和接收速率匹配
如果需要使用缓冲通道,代码可以改为:
// 使用缓冲通道
ch := make(chan []int, numberArray)
for i := 0; i < numberArray; i++ {
go func(sli []int, i int, ch chan<- []int) {
sort.Sort(sort.IntSlice(sli))
ch <- sli
}(slices[i], i, ch)
}
// 所有goroutine可以快速发送而不阻塞
// 然后主goroutine一次性接收所有结果
for i := 0; i < numberArray; i++ {
result = append(result, <-ch...)
}
关键点总结
- 无缓冲通道确保发送和接收同步
- 主goroutine的接收循环会阻塞等待每个结果
- 结果顺序不一定与goroutine启动顺序相同(取决于哪个goroutine先完成)
- 使用
...操作符可以将切片展开为多个参数进行append操作
你的代码是正确的,它利用了Go通道的阻塞特性来同步并发操作的结果收集。


