频道封锁顺序

时间:2019-05-30 13:43:33

标签: go channel

我试图了解频道在golang中的工作方式。我拥有的代码非常简单,但输出令人惊讶。

正如文档所述:从通道进行读写操作会阻塞当前的goroutine,因此我认为写入通道会阻塞该通道,直到主例程产生为止。

package main

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    for msg := range messages {
        println(msg)
    }
}

我认为它将打印

p1
ping1
p2
ping2

但实际上可以打印

p1
p2
ping1
ping2

1 个答案:

答案 0 :(得分:2)

您正在使用无缓冲通道,该通道用作主goroutine与第二goroutine之间的同步点。

在这种情况下,您仅知道当第二个goroutine在messages <- "ping1"处时,主要的例程在for msg := range messages行处。因此,不能保证主循环立即到达println(msg)。也就是说,与此同时,第二个goroutine可能继续前进并到达了println("p2")messages <- "ping2"行。

作为反例,我添加了一个通道以强制在打印之间完全同步。

package main

func rtn(messages chan<- string, syncChan chan struct{}) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //Wait for main goroutine to print its message
    <-syncChan

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"

    //Wait for main goroutine to print its message
    <-syncChan
}

func main() {
    messages := make(chan string)
    syncChan := make(chan struct{})
    go rtn(messages, syncChan)

    for msg := range messages {
        println(msg)
        //Notify the second goroutine that is free to go
        syncChan <- struct{}{}
    }
}

输出您期望的输出:

p1
ping1
p2
ping2

这里是产生所需输出的另一个示例。在这种情况下,主goroutine被time.Sleep()强制阻止。这将使第二个goroutine准备好在接收器准备好接收之前发送。因此,发送者实际上将阻止发送操作。

package main

import (
    "time"
)

func rtn(messages chan<- string) {
    defer close(messages)

    println("p1")
    messages <- "ping1"

    //for i := 0; i < 10000000; i++ { }

    println("p2")
    messages <- "ping2"
}

func main() {
    messages := make(chan string)
    go rtn(messages)

    //Put main goroutine to sleep. This will make the
    //sender goroutine ready before the receiver. 
    //Therefore it will have to actually block!
    time.Sleep(time.Millisecond * 500)

    for msg := range messages {
        println(msg)
    }
}