V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Grocker
V2EX  ›  Go 编程语言

为什么这段代码能够顺序的打印出 1-10,没太理解?

  •  
  •   Grocker · 22 天前 · 3524 次点击

    问了 ds, 解释的有点 get 不到,求大佬详细解释下

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main() {
    	var wg sync.WaitGroup
    	prev := make(chan struct{})
    
    	current := make(chan struct{})
    
    	prev = current
    
    	for id := 1; id <= 10; id++ {
    		wg.Add(1)
    
    		next := make(chan struct{})
    
    		go func(id int, prev <-chan struct{}, next chan<- struct{}) {
    			defer wg.Done()
    			<-prev
    
    			fmt.Println(id)
    
    			if id < 10 {
    				close(next)
    			}
    		}(id, prev, next)
    		prev = next
    
    	}
    	close(current)
    	wg.Wait()
    }
    
    
    
    9 条回复    2025-07-08 12:33:35 +08:00
    LitterGopher
        1
    LitterGopher  
       22 天前
    關鍵在於 `next := make(chan struct{})` 這一行代碼, 每次循環都會創建一個新的 chan, 在閉包中只有等 prev 被寫入/或關閉的時候讀取纔會結束, 然後纔會繼續後面的內容, 然而這個 prev 每次都被 next 重新填充, 所以除了第一個 goroutine 後面的所有 goroutine 都是上一個循環創建的 next. 所以要等上一個 next 關閉或被寫入. 所以只有在第一個被寫入後才能依次出發後面的 goroutine 執行, 而每一個 goroutine 中 close(next) 實際上就是在對下一個 goroutine close(prev)
    LitterGopher
        2
    LitterGopher  
       22 天前
    這寫法雖然有點意思, 但是不推薦這樣寫.
    yuntun
        3
    yuntun  
       22 天前
    通过 prev <-chan struct{}和 next chan<- struct{} 的机制, id=1 的 goroutine 等待 current 被关闭,最先被唤醒;
    然后打印 1 ,然后关闭 next , 也就是传给 id=2 的 prev, id=2 的 goroutine 被唤醒,打印 2 ,然后关闭他的 next , 整个链条顺序唤醒,顺序打印,直到 id=10 。
    bronyakaka
        4
    bronyakaka  
       22 天前
    每个 goroutine 的 prev channel 是前一个 goroutine 的 next channel 。每个 goroutine 在执行打印之前会等待从 prev channel 接收信号,打印完成后会向 next channel 发送信号(通过关闭 channel )。
    iceheart
        5
    iceheart  
       22 天前 via Android
    就像,烽火台
    zxjxzj9
        6
    zxjxzj9  
       21 天前
    就像接力棒,第一棒( current )在循环外交给了第一个 goroutine ,然后在每个 goroutine 里都把当前这个接力棒小给下一个 goroutine 后退出。程序干的事情实际是一口气开 10 个 goroutine (好比跑道上已经准备好了 10 个人),然后在循环外把第一个 channel 丢给了 id 是 1 的 goroutine ,之后依次往下接力
    AkinoKaedeChan
        7
    AkinoKaedeChan  
       21 天前 via iPhone
    类似 PV 操作,把 close 看成 V 操作,把 <- 看成 P 操作,变量被反复修改了,其实可以看成共 10 个信号量。
    zzhaolei
        8
    zzhaolei  
       21 天前
    击鼓传花。可以这么写,没必要创建一个新的 channel ,还需要回收:
    ```
    prev := make(chan struct{})
    start := prev
    ...

    close(start)
    ```
    minchieh
        9
    minchieh  
       20 天前   ❤️ 1
    基础知识:chan 类型变量是引用传递(子函数给赋值 会改变原值)
    加入 2 行代码。真相就出来了
    ------------------------------------------------------------------
    go func(id int, prev <-chan struct{}, next chan<- struct{}) {
    defer wg.Done()
    fmt.Printf("我是%d 我在等%p\n", id, prev)
    <-prev

    fmt.Println(id)

    if id < 10 {
    fmt.Printf("========我是%d 我放行了%p\n", id, next)
    close(next)
    }
    }(id, prev, next)

    --------------------------------------------------------------------------------
    看打印:
    初始的 prev:0x140000221c0
    我是 10 我在等 0x140000225b0
    我是 6 我在等 0x140000223f0
    我是 7 我在等 0x14000022460
    我是 8 我在等 0x140000224d0
    我是 9 我在等 0x14000022540
    我是 2 我在等 0x14000022230
    我是 4 我在等 0x14000022310
    我是 5 我在等 0x14000022380
    我是 1 我在等 0x140000221c0
    1
    我是 3 我在等 0x140000222a0
    ========我是 1 我放行了 0x14000022230
    2
    ========我是 2 我放行了 0x140000222a0
    3
    ========我是 3 我放行了 0x14000022310
    4
    ========我是 4 我放行了 0x14000022380
    5
    ========我是 5 我放行了 0x140000223f0
    6
    ========我是 6 我放行了 0x14000022460
    7
    ========我是 7 我放行了 0x140000224d0
    8
    ========我是 8 我放行了 0x14000022540
    9
    ========我是 9 我放行了 0x140000225b0
    10
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4697 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 04:00 · PVG 12:00 · LAX 21:00 · JFK 00:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.