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

7 回复

感谢你清晰的回答,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语言中的其他结构) 是的,第一次理解起来确实有点困难……

非常感谢你提供的链接,它极大地帮助我理解了其工作原理:

感谢:

当一个新 Goroutine 启动时,goroutine 调用会立即返回。与函数不同,程序控制不会等待 Goroutine 执行完毕。控制会立即返回到 Goroutine 调用之后的下一行代码,并且 Goroutine 的任何返回值都会被忽略。

以及

Go Playground - The Go Programming Language

(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完成。具体流程:

  1. 循环开始,执行<-ch(阻塞等待)
  2. 当第一个goroutine发送数据时,接收第一个结果
  3. 继续循环,再次执行<-ch(阻塞等待下一个结果)
  4. 重复直到接收完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...)
}

关键点总结

  1. 无缓冲通道确保发送和接收同步
  2. 主goroutine的接收循环会阻塞等待每个结果
  3. 结果顺序不一定与goroutine启动顺序相同(取决于哪个goroutine先完成)
  4. 使用...操作符可以将切片展开为多个参数进行append操作

你的代码是正确的,它利用了Go通道的阻塞特性来同步并发操作的结果收集。

回到顶部