从缓冲通道和非缓冲通道中进行选择

huangapple go评论81阅读模式
英文:

Selecting from buffered vs non-buffered channel

问题

当我运行以下简单代码时,它按预期打印出"ping":

    messages := make(chan string)

    go func() { messages <- "ping" }()
    fmt.Println(<-messages)

然而,当我在使用相同的非缓冲通道与select语句时,它没有填充通道,因此打印出"no message sent":

    messages := make(chan string)

    select {
    case messages <- "ping":
        fmt.Println("sent message")
    default:
        fmt.Println("no message sent")
    }

为什么会发生这种情况?通道是相同的,但是通过goroutine可以访问,而通过select不能访问。

此外,我发现当我将其转换为带有缓冲区(大小为1)的通道时,它可以成功填充通道:为什么?

    messages := make(chan string, 1)

    select {
    case messages <- "ping":
        fmt.Println("sent message")
    default:
        fmt.Println("no message sent")
    }

请注意,没有任何阻塞,代码立即按照我所描述的返回结果。

英文:

When I'm running the following simple code it prints "ping" as expected:

    messages := make(chan string)

    go func() { messages &lt;- &quot;ping&quot; }()
    fmt.Println(&lt;-messages)

However, when I'm using the same non-buffered channel with select, it's not fulfilling it with a ping, thus printing "no message sent":

    messages := make(chan string)

    select {
    case messages &lt;- &quot;ping&quot;:
        fmt.Println(&quot;sent message&quot;)
    default:
        fmt.Println(&quot;no message sent&quot;)
    }

Why this is happening? The channels are the same, but it's accessible with goroutine but not with select.

In addition, I've found out that when I'm converting it to a buffered channel (with size 1) it fulfills the channel like a charm: Why?

    messages := make(chan string,1)

    select {
    case messages &lt;- &quot;ping&quot;:
        fmt.Println(&quot;sent message&quot;)
    default:
        fmt.Println(&quot;no message sent&quot;)
    }

Note nothing is hanging up, but just immediately returns as I've described.

答案1

得分: 4

select语句会阻塞,直到任意一个case准备就绪,或者如果没有准备就绪的case,则执行default语句。

在你的第二个程序中,在select之前没有出现接收操作,所以case messages <- "ping"不准备就绪——没有接收者——因此总是执行default语句。

使用带缓冲的通道,即使在另一端没有接收者,发送操作也不会阻塞,所以case messages <- "ping"准备就绪并执行。

在带有goroutine的代码片段中,发送操作并发运行,因此主程序流程可以继续执行fmt.Println调用,并在<-messages上阻塞,直到并发发送在通道上提供一个值。

英文:

The select statement blocks until any one of the cases is ready, or it runs the default case if none is ready.

In your second program, no receive operation appears before select, so the case messages &lt;- &quot;ping&quot; is not ready — there is no receiver —, and default is always executed.

With a buffered channel, the send operation does not block even if there's no receiver on the other end, so case messages &lt;- &quot;ping&quot; is ready and runs.

In the snippet with the goroutine, the send operation runs concurrently, so the main program flow can move on to the fmt.Println call and block on &lt;-messages, until the concurrent send makes a value available on the channel

答案2

得分: 1

go需要一些类似于“检查点”(阻塞语句或其他goroutine中的超时)来在使用无缓冲通道类型时切换执行的goroutine。当使用无缓冲通道时,通道将被阻塞,直到有其他地方读取它。当主go例程进入select语句时,select语句将被阻塞,直到某个case中发生某种活动(消息)或释放操作。由于你的case读取操作尚未完成,通道被阻塞,因为无缓冲通道需要同时进行读取和写入才能充当检查点,所以select语句跳转到默认语句。

当使用缓冲通道时,任何活动都会向通道发送信号,但只有在缓冲区满时才会被阻塞。因此,当使用缓冲通道时,不需要阻塞读取操作,因此select语句会检测到通道上的数据可用。

此外,在以下代码中:

messages := make(chan string)
go func() { messages <- "ping" }()
fmt.Println(<-messages)

你使用<-messages读取通道并等待。

没有对通道进行读取会使无缓冲通道保持阻塞:

messages := make(chan string)
select {
    case messages <- "ping":
        fmt.Println("sent message")
    default:
        fmt.Println("no message sent")
}

缓冲通道不会阻塞,因此写入操作将被执行:

messages := make(chan string, 1)
select {
case messages <- "ping":
    fmt.Println("sent message")
default:
    fmt.Println("no message sent")
}
英文:

go needs some sort of "checkpoints" (blocking statements or timeout in other goroutines) to switch the executing goroutine when you use the Unbuffered channel type channels will be blocked until something reads it, when the main go-routine enters the select statement, select blocks until there is some activity(message) or release happens in one of the cases and since your case read is not done yet, channel blocks because unbuffered channels need both read and write to act as checkpoint, so selects jumps to default statement.
when you use buffered channels, any activity will send a signal over the channel but it won't be blocked until the buffer is full. so when buffered channels are used, there is no need for the reader to be blocked, so select detects a data to work with on the channel.

also in

messages := make(chan string)
go func() { messages &lt;- &quot;ping&quot; }()
fmt.Println(&lt;-messages)

you waited on the channel with <-messages read

No reads on channel keeps Unbuffered channel, blocked:

messages := make(chan string)
select {
    case messages &lt;- &quot;ping&quot;:
        fmt.Println(&quot;sent message&quot;)
    default:
        fmt.Println(&quot;no message sent&quot;)
    }

buffered channels do not block so write action will be preformed:

messages := make(chan string,1)
select {
case messages &lt;- &quot;ping&quot;:
    fmt.Println(&quot;sent message&quot;)
default:
    fmt.Println(&quot;no message sent&quot;)
}

huangapple
  • 本文由 发表于 2021年8月10日 19:11:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/68725744.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定