Golang Go语言中多个协程读取同一个channel,怎么不是按顺序打印

发布于 1周前 作者 wuwangju 来自 Go语言

Golang Go语言中多个协程读取同一个channel,怎么不是按顺序打印

func main(){

ch := make(chan int)

go func(){
	for{
		c :=<-ch
		fmt.Println("one:",c,"len:",len(ch))
	}
}()

go func(){
	for{
		c :=<-ch
		fmt.Println("two:",c,"len:",len(ch))
	}
}()

for i:=1;i<=100;i++{
	ch<-i
}

time.Sleep(time.Second * time.Duration(2))

}


更多关于Golang Go语言中多个协程读取同一个channel,怎么不是按顺序打印的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

32 回复

你把工作分给两个工人去做,工人 1 和工人 2 的完成的先后顺序是不确定的

更多关于Golang Go语言中多个协程读取同一个channel,怎么不是按顺序打印的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


因为它们是 X 个 goroutine 在执行

按顺序用协程干啥?

读取 channel 的数据顺序是顺序读取的
但是你的 goroutine 运行顺序
由调度器调度的 不受你的控制
除非你能做到同步

<-ch 不是会阻塞么,我想着它应该是在协程中接收到 1 打印出来了 然后才能接收到 2 再打印,现在给我感觉是 接收和打印不是一个原子操作

先接受不一定先打印啊

必然不是原子啊

无脑猜 stdout print 应该算是 IO 操作, IO 操作时 goroutine 切换了.

ch<-i 后面 sleep 一下

我觉得这个说法能想通,接收和打印应该是一起运行了,只是 print 是 IO 操作

我在 fmt.Println 之后 sleep 了,打印就 1-100 了

你给你的管道缓冲长度设置成 1 就是顺序的了

你没设置长度,所以 ch<-i 不会阻塞

不能看不注重代码格式的人写的代码

https://play.golang.org/p/zoNa2QRR6F- 然而不是……
没设置长度的话我记得是没接收方就阻塞,有接收方转移到接收方
而且就算设置长度输出仍然是混乱的

似乎每个 goroutine 有单独的输出缓冲区,再用一个 channel 去集中的话就可以
https://play.golang.org/p/Xa-lwecfveV
但不是很懂没设置长度但有集中的情况下为什么还是乱的。

可能不是单独缓冲区一说,用令牌方法保证两个 goroutine 交替运行以后立即保证了输出也是 one two 交替的……
而且 Println 应当会 flush ?

这里的 channel 是无缓冲通道,会阻塞的,所以值是按照顺序从 channel 中取出来的,但是取值和打印不是原子的,中间调度器发生调度导致打印的结果不是顺序的。

因为你 goroutine 调度不是顺序执行的

package main

import (
“fmt”
“sync”
“time”
)

func main() {
loca := sync.Mutex{}
ch := make(chan int)

go func() {
for {
loca.Lock()
{
c := <-ch
fmt.Println(“one:”, c, “len:”, len(ch))
}
loca.Unlock()

}
}()

go func() {
for {
loca.Lock()
{
c := <-ch
fmt.Println(“two:”, c, “len:”, len(ch))
}
loca.Unlock()
}
}()

for i := 1; i <= 100; i++ {
ch <- i
}

time.Sleep(time.Second * time.Duration(2))
}


这样就好了

接收到的顺序必然是按顺序来的,但是哪个协程先读到,或先写出,是控制不了的

#11 用 sleep 并不是完美的同步方法,虽然将 sleep 的时间设置的长一点可以在很大程度上得到顺序输出,但本质上由于 goroutine 调度器的原因还是会可能出现乱序输出情况,因为哪个 goroutine 先执行取决于调度器。正确的做法是用一个无 buffer 的 channel 去做两个 goroutine 之间的同步

用单核 cpu 可能得到顺序结果吧

<br>var lock1 sync.Mutex<br>var lock2 sync.Mutex<br><br>func main() {<br> ch := make(chan int)<br><br> lock2.Lock()<br> go func() {<br> for {<br> lock1.Lock()<br> c := &lt;-ch<br> fmt.Println("one:", c, "len:", len(ch))<br> lock2.Unlock()<br> }<br> }()<br><br> go func() {<br> for {<br> lock2.Lock()<br> c := &lt;-ch<br> fmt.Println("two:", c, "len:", len(ch))<br> lock1.Unlock()<br> }<br> }()<br><br> for i := 1; i &lt;= 100; i++ {<br> ch &lt;- i<br> }<br><br> time.Sleep(time.Second * time.Duration(2))<br>}<br><br>

协程的调用你没有办法保证是顺序调用

两个人在窗口取餐,谁先谁后肯定要争一争了。

channel 只是顺序放在了一个 queue 里,多个 worker 每次谁去取不一定。

为什么绿皮车比高铁发车早,但是比高铁到的晚呢,这就是原因

可以试试 go func 的最后 runtime.Gosched()一下

接收是原子的,接收+打印不是

在Go语言中,多个协程读取同一个channel时,打印顺序并不保证与发送顺序一致,这是因为channel的读取操作在多个协程之间是并发的。Go的调度器会根据系统的资源情况和协程的状态来安排协程的执行,这种调度是非确定性的。

具体来说,当多个协程阻塞在同一个无缓冲(或缓冲已满)的channel上时,哪个协程能首先读取到数据是由Go的运行时调度器决定的,这通常取决于协程被唤醒时的调度顺序和操作系统的线程调度策略。

如果你希望保持数据的读取顺序,可以考虑以下几种方法:

  1. 使用带缓冲的channel并手动控制读取顺序:但这通常会增加代码的复杂性和潜在的同步问题。

  2. 使用单协程读取然后分发:由一个单独的协程负责从channel读取数据,然后通过其他机制(如另一个channel或sync.WaitGroup)将数据分发给其他协程处理。

  3. 使用同步原语:如互斥锁(sync.Mutex)或条件变量(sync.Cond)来控制对channel的访问,但这会破坏channel的并发特性,降低性能。

在实际应用中,应根据具体需求选择最适合的方法。通常,对于大多数并发场景,Go的channel已经提供了足够的灵活性来构建高效且正确的并发程序。

回到顶部