Golang中channel的顺序问题

Golang中channel的顺序问题 我不理解为什么不能按我想要的任意顺序从通道读取数据。我已经向通道发送了值。为什么必须保持相同的顺序?谢谢

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go send(ch1, ch2)
    
    // 这个顺序是有效的
    fmt.Println(<- ch1)
    fmt.Println(<- ch2)
    
    // 这个顺序是无效的
    fmt.Println(<- ch1)
    fmt.Println(<- ch1)
}

func send(ch1, ch2 chan<- int) {
    for i := 0; i <= 10; i++ {
        if i % 2 == 0 {
            ch1 <- i
        } else {
            ch2 <- i
        }
    }
    close(ch1)
    close(ch2)
}

更多关于Golang中channel的顺序问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

这说得通!谢谢!

更多关于Golang中channel的顺序问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


但是为什么我不能先读取第一个通道的值,然后再开始读取第二个通道呢?为什么必须保持顺序?我还是有点困惑……

可以以任意顺序读取它们,不过你使用的是无缓冲通道,这意味着你的“send”协程会“休眠”,直到你从该通道读取了值。

如果你使用缓冲通道,效果会更好。

您可以按任意顺序读取,但要能够从通道读取,您需要先向其写入数据。

当前您有一个 goroutine 向两个通道中的一个写入数据。由于这些通道是无缓冲的,该 goroutine 在当前读取操作完成之前不会向另一个通道写入数据。

当您尝试首先从尚未写入数据的通道读取时,读取器也会阻塞,直到通道上有数据为止。

调度器识别出有两个 goroutine 都在等待解除阻塞。调度器会取消程序,因为它知道这两个 goroutine 的需求都无法得到满足。

在Go语言中,通道(channel)的发送和接收操作是顺序保证的,但多个通道之间的接收顺序取决于goroutine调度和通道状态。你的代码存在两个关键问题:

  1. 通道关闭后继续接收:当通道关闭后,从该通道接收会立即返回零值,导致重复读取
  2. 通道间顺序不确定:多个通道的接收顺序由运行时调度决定

这是修正后的示例,展示如何按任意顺序从不同通道接收:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go send(ch1, ch2)

	// 使用select实现任意顺序接收
	for i := 0; i < 11; i++ {
		select {
		case v, ok := <-ch1:
			if ok {
				fmt.Printf("从ch1接收: %d\n", v)
			}
		case v, ok := <-ch2:
			if ok {
				fmt.Printf("从ch2接收: %d\n", v)
			}
		}
	}
}

func send(ch1, ch2 chan<- int) {
	for i := 0; i <= 10; i++ {
		if i%2 == 0 {
			ch1 <- i
			time.Sleep(100 * time.Millisecond) // 模拟处理延迟
		} else {
			ch2 <- i
			time.Sleep(50 * time.Millisecond) // 模拟处理延迟
		}
	}
	close(ch1)
	close(ch2)
}

输出顺序可能为:

从ch2接收: 1
从ch1接收: 0
从ch2接收: 3
从ch1接收: 2
从ch2接收: 5
从ch1接收: 4
...

如果要实现完全任意的接收顺序,可以使用带缓冲的通道和select:

func main() {
	ch1 := make(chan int, 10)
	ch2 := make(chan int, 10)

	go send(ch1, ch2)

	// 随机顺序接收
	for {
		select {
		case v, ok := <-ch1:
			if !ok {
				ch1 = nil // 设置为nil后select将忽略此case
				continue
			}
			fmt.Printf("从ch1接收: %d\n", v)
		case v, ok := <-ch2:
			if !ok {
				ch2 = nil
				continue
			}
			fmt.Printf("从ch2接收: %d\n", v)
		default:
			if ch1 == nil && ch2 == nil {
				return
			}
		}
	}
}

关键点:

  • 单个通道保证FIFO顺序
  • 多个通道间使用select可实现非确定性的接收顺序
  • 通道关闭后需处理零值问题
  • 带缓冲通道允许发送和接收解耦
回到顶部