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 · 5284 次点击
    这是一个创建于 1427 天前的主题,其中的信息可能已经有所发展或是发生改变。

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


    这样就好了
    php01
        20
    php01  
       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   ·   实用小工具   ·   5239 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 09:12 · PVG 17:12 · LAX 01:12 · JFK 04:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.