Golang Go语言中多个协程读取同一个channel,怎么不是按顺序打印
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
你把工作分给两个工人去做,工人 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 := <-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 := <-ch<br> fmt.Println("two:", c, "len:", len(ch))<br> lock1.Unlock()<br> }<br> }()<br><br> for i := 1; i <= 100; i++ {<br> ch <- i<br> }<br><br> time.Sleep(time.Second * time.Duration(2))<br>}<br><br>
并不会
协程的调用你没有办法保证是顺序调用
两个人在窗口取餐,谁先谁后肯定要争一争了。
底层是锁
为什么绿皮车比高铁发车早,但是比高铁到的晚呢,这就是原因
可以试试 go func 的最后 runtime.Gosched()一下
接收是原子的,接收+打印不是
在Go语言中,多个协程读取同一个channel时,打印顺序并不保证与发送顺序一致,这是因为channel的读取操作在多个协程之间是并发的。Go的调度器会根据系统的资源情况和协程的状态来安排协程的执行,这种调度是非确定性的。
具体来说,当多个协程阻塞在同一个无缓冲(或缓冲已满)的channel上时,哪个协程能首先读取到数据是由Go的运行时调度器决定的,这通常取决于协程被唤醒时的调度顺序和操作系统的线程调度策略。
如果你希望保持数据的读取顺序,可以考虑以下几种方法:
-
使用带缓冲的channel并手动控制读取顺序:但这通常会增加代码的复杂性和潜在的同步问题。
-
使用单协程读取然后分发:由一个单独的协程负责从channel读取数据,然后通过其他机制(如另一个channel或sync.WaitGroup)将数据分发给其他协程处理。
-
使用同步原语:如互斥锁(sync.Mutex)或条件变量(sync.Cond)来控制对channel的访问,但这会破坏channel的并发特性,降低性能。
在实际应用中,应根据具体需求选择最适合的方法。通常,对于大多数并发场景,Go的channel已经提供了足够的灵活性来构建高效且正确的并发程序。