golang 多个协程中 读取同一个 channel,怎么不是按顺序打印 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
hyp1002950
V2EX    Go 编程语言

golang 多个协程中 读取同一个 channel,怎么不是按顺序打印
  •  
  •   hyp1002950 2021-01-22 11:26:17 +08:00 5769 次点击

    这是一个创建于 1731 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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)) 

    }

    第 1 条附言    2021-01-22 22:11:07 +08:00
    var mu sync.Mutex func Println(v ...interface{}){ mu.Lock() fmt.Println(v...) mu.Unlock() } func main(){ ch := make(chan int) go func(){ for{ c :=<-ch Println("one:",c,"len:",len(ch)) } }() go func(){ for{ c :=<-ch Println("two:",c,"len:",len(ch)) } }() for i:=1;i<=100;i++{ ch<-i } time.Sleep(time.Second * time.Duration(2)) } /* 输出 two: 1 len: 0 two: 3 len: 0 two: 4 len: 0 two: 5 len: 0 two: 6 len: 0 two: 7 len: 0 one: 2 len: 0 two: 8 len: 0 two: 10 len: 0 two: 11 len: 0 two: 12 len: 0 one: 9 len: 0 two: 13 len: 0 two: 15 len: 0 two: 16 len: 0 one: 14 len: 0 one: 18 len: 0 ... 结论:channel的接收和 之后的代码不是一起同时执行,不具有原子性,当channel被接收的时候,协程切换了 */ 
    31 条回复    2021-01-23 11:56:42 +08:00
    cloverzrg2
        1
    cloverzrg2  
       2021-01-22 11:31:13 +08:00
    你把工作分给两个工人去做,工人 1 和工人 2 的完成的先后顺序是不确定的
    jazzychai
        2
    jazzychai  
       2021-01-22 11:32:03 +08:00
    因为它们是 X 个 goroutine 在执行
    huobazi
        3
    huobazi  
       2021-01-22 11:32:48 +08:00
    按顺序用协程干啥?
    beidounanxizi
        4
    beidounanxizi  
       2021-01-22 11:34:45 +08:00
    读取 channel 的数据顺序是顺序读取的
    但是你的 goroutine 运行顺序
    由调度器调度的 不受你的控制
    除非你能做到同步
    hyp1002950
        5
    hyp1002950  
    OP
       2021-01-22 11:38:17 +08:00
    <-ch 不是会阻塞么,我想着它应该是在协程中接收到 1 打印出来了 然后才能接收到 2 再打印,现在给我感觉是 接收和打印不是一个原子操作
    yzbythesea
        6
    yzbythesea  
       2021-01-22 11:41:06 +08:00
    先接受不一定先打印啊
    yzbythesea
        7
    yzbythesea  
       2021-01-22 11:41:22 +08:00
    必然不是原子啊
    cxh116
        8
    cxh116  
       2021-01-22 11:41:31 +08:00
    无脑猜 stdout print 应该算是 IO 操作, IO 操作时 goroutine 切换了.
    zxlzy
        9
    zxlzy  
       2021-01-22 11:42:07 +08:00
    ch<-i 后面 sleep 一下
    hyp1002950
        10
    hyp1002950  
    OP
       2021-01-22 11:52:10 +08:00
    @cxh116 我觉得这个说法能想通,接收和打印应该是一起运行了,只是 print 是 IO 操作
    hyp1002950
        11
    hyp1002950  
    OP
       2021-01-22 11:57:42 +08:00
    @zxlzy 我在 fmt.Println 之后 sleep 了,打印就 1-100 了
    sadfQED2
        12
    sadfQED2  
       2021-01-22 12:02:25 +08:00 via Android
    你给你的管道缓冲长度设置成 1 就是顺序的了
    sadfQED2
        13
    sadfQED2  
       2021-01-22 12:03:52 +08:00 via Android
    你没设置长度,所以 ch<-i 不会阻塞
    towry
        14
    towry  
       2021-01-22 13:49:36 +08:00
    不能看不注重代码格式的人写的代码
    no1xsyzy
        15
    no1xsyzy  
       2021-01-22 14:06:42 +08:00
    @sadfQED2 https://play.golang.org/p/zoNa2QRR6F- 然而不是……
    没设置长度的话我记得是没接收方就阻塞,有接收方转移到接收方
    而且就算设置长度输出仍然是混乱的

    似乎每个 goroutine 有单独的输出缓冲区,再用一个 channel 去集中的话就可以
    https://play.golang.org/p/Xa-lwecfveV
    但不是很懂没设置长度但有集中的情况下为什么还是乱的。
    no1xsyzy
        16
    no1xsyzy  
       2021-01-22 14:26:49 +08:00
    @no1xsyzy 可能不是单独缓冲区一说,用令牌方法保证两个 goroutine 交替运行以后立即保证了输出也是 one two 交替的……
    而且 Println 应当会 flush ?
    oluoluo
        17
    oluoluo  
       2021-01-22 16:59:13 +08:00
    这里的 channel 是无缓冲通道,会阻塞的,所以值是按照顺序从 channel 中取出来的,但是取值和打印不是原子的,中间调度器发生调度导致打印的结果不是顺序的。
    janxin
        18
    janxin  
       2021-01-22 17:03:05 +08:00
    因为你 goroutine 调度不是顺序执行的
    qq1340691923
        19
    qq1340691923  
       2021-01-22 17:05:27 +08:00
    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))
    }


    这样就好了
    anteros
        20
    anteros  
       2021-01-22 17:05:53 +08:00
    接收到的顺序必然是按顺序来的,但是哪个协程先读到,或先写出,是控制不了的
    Nitroethane
        21
    Nitroethane  
       2021-01-22 17:08:12 +08:00
    @hyp1002950 #11 用 sleep 并不是完美的同步方法,虽然将 sleep 的时间设置的长一点可以在很大程度上得到顺序输出,但本质上由于 goroutine 调度器的原因还是会可能出现乱序输出情况,因为哪个 goroutine 先执行取决于调度器。正确的做法是用一个无 buffer 的 channel 去做两个 goroutine 之间的同步
    keepeye
        22
    keepeye  
       2021-01-22 17:18:22 +08:00
    用单核 cpu 可能得到顺序结果吧
    kele1997
        23
    kele1997  
       2021-01-22 17:52:46 +08:00
    ```
    var lock1 sync.Mutex
    var lock2 sync.Mutex

    func main() {
    ch := make(chan int)

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

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

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

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

    ```
    KaynW
        24
    KaynW  
       2021-01-22 18:01:07 +08:00
    @keepeye 并不会
    back0893
        25
    back0893  
       2021-01-22 19:09:25 +08:00
    协程的调用你没有办法保证是顺序调用
    zhyl
        26
    zhyl  
       2021-01-22 20:32:11 +08:00
    两个人在窗口取餐,谁先谁后肯定要争一争了。
    Takamine
        27
    Takamine  
       2021-01-22 21:01:59 +08:00 via Android
    channel 只是顺序放在了一个 queue 里,多个 worker 每次谁去取不一定。
    useben
        28
    useben  
       2021-01-23 09:24:37 +08:00
    底层是锁
    kifile
        29
    kifile  
       2021-01-23 09:49:14 +08:00
    为什么绿皮车比高铁发车早,但是比高铁到的晚呢,这就是原因
    zhuzeitou
        30
    zhuzeitou  
       2021-01-23 10:16:14 +08:00 via Android
    可以试试 go func 的最后 runtime.Gosched()一下
    adamwong
        31
    adamwong  
       2021-01-23 11:56:42 +08:00
    接收是原子的,接收+打印不是
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2579 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 07:53 PVG 15:53 LAX 00:53 JFK 03:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86