英文:
In a select statement, the buffer in channel will be used firstly or the pending queue value will be used firstly?
问题
最近我一直在阅读Go语言中select
的源代码。有一部分代码我不太理解。
例如,当被选择的通道存在待发送操作或待接收操作时,它首先使用待发送的值而不是通道缓冲区中的值。
// pass 1 - look for something already waiting
var dfli int
var dfl *scase
var casi int
var cas *scase
var recvOK bool
for i := 0; i < ncases; i++ {
casi = int(pollorder[i])
cas = &scases[casi]
c = cas.c
switch cas.kind {
case caseNil:
continue
case caseRecv:
sg = c.sendq.dequeue()
if sg != nil {
goto recv
}
if c.qcount > 0 {
goto bufrecv
}
if c.closed != 0 {
goto rclose
}
case caseSend:
if raceenabled {
racereadpc(c.raceaddr(), cas.pc, chansendpc)
}
if c.closed != 0 {
goto sclose
}
sg = c.recvq.dequeue()
if sg != nil {
goto send
}
if c.qcount < c.dataqsiz {
goto bufsend
}
case caseDefault:
dfli = casi
dfl = cas
}
}
因此,我编写了一些简单的代码来验证我的假设。但结果表明我是错误的。
package main
import (
"fmt"
"time"
)
func main() {
var mark chan int
length := 3
c := make(chan int, length)
for i := 1; i <= length; i++ {
c <- i
}
go func() {
c <- 4
}()
time.Sleep(time.Millisecond * 10)
select {
case val := <-c:
fmt.Println("receive val is ", val)
case _ = <-mark:
fmt.Println("block chan")
}
}
结果是receive val is 1
。然而,我认为打印输出应该是receive val is 4
。似乎select
首先使用缓冲区中的值。
英文:
Recently I've been reading the source code of select
in Go. There is some part of the code that I don't understand.
For example, when the channel being selected has a pending send operation, or a pending receive operation, it first uses the value from pending send instead of the value in channel buffer.
// pass 1 - look for something already waiting
var dfli int
var dfl *scase
var casi int
var cas *scase
var recvOK bool
for i := 0; i < ncases; i++ {
casi = int(pollorder[i])
cas = &scases[casi]
c = cas.c
switch cas.kind {
case caseNil:
continue
case caseRecv:
sg = c.sendq.dequeue()
if sg != nil {
goto recv
}
if c.qcount > 0 {
goto bufrecv
}
if c.closed != 0 {
goto rclose
}
case caseSend:
if raceenabled {
racereadpc(c.raceaddr(), cas.pc, chansendpc)
}
if c.closed != 0 {
goto sclose
}
sg = c.recvq.dequeue()
if sg != nil {
goto send
}
if c.qcount < c.dataqsiz {
goto bufsend
}
case caseDefault:
dfli = casi
dfl = cas
}
}
So I wrote some simple code to verify my assumption. But the result implies I'm wrong.
package main
import (
"fmt"
"time"
)
func main() {
var mark chan int
length := 3
c := make(chan int, length)
for i := 1; i <= length; i++ {
c <- i
}
go func() {
c <- 4
}()
time.Sleep(time.Millisecond * 10)
select {
case val := <-c:
fmt.Println("receive val is ", val)
case _ = <-mark:
fmt.Println("block chan")
}
}
The result is receive val is 1
. However, I think the printed output should be receive val is 4
. It seems that the select
first uses value in buffer.
答案1
得分: 3
你错误地阅读了源代码。
代码中的goto recv
跳转到了一个标记为recv
的代码块,其中包含以下内容:
recv:
// 可以从休眠的发送方(sg)接收
recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
if debugSelect {
print("syncrecv: cas0=", cas0, " c=", c, "\n")
}
recvOK = true
goto retc
如果你查看recv
函数(https://golang.org/src/runtime/chan.go#L607),该函数定义在channel.go
中,你会发现它会从缓冲区中读取数据(如果有的话)。根据该函数的文档:
// recv 在一个满的通道 c 上执行接收操作。
// 有两个部分:
// 1)发送方 sg 发送的值被放入通道中,并唤醒发送方继续执行。
// 2)接收方(当前的 G)接收到的值被写入 ep。
// 对于同步通道,这两个值是相同的。
// 对于异步通道,接收方从通道缓冲区获取数据,发送方的数据被放入通道缓冲区。
// 通道 c 必须是满的并且已锁定。recv 使用 unlockf 解锁 c。
// sg 必须已从 c 中出队。
// 非空的 ep 必须指向堆或调用者的栈。
综上所述,所讨论的情况只是用于判断是否有发送方在等待,这意味着要么通道是无缓冲的,要么通道的缓冲区已满;而与接收方接收到的值无关。
英文:
You read the source code wrong.
The code goto recv
goes to a code block labelled recv
, which is:
recv:
// can receive from sleeping sender (sg)
recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
if debugSelect {
print("syncrecv: cas0=", cas0, " c=", c, "\n")
}
recvOK = true
goto retc
And if you look in recv
function (https://golang.org/src/runtime/chan.go#L607), which is defined in channel.go
, you would see it read from the buffer if there is one. From the document of that function:
// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
// and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
// written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
To conclude it, the case in question is just to see if there is a sender waiting on it, which means either due to the channel is unbufferred or the buffer of the channel is full; and it has nothing to do what value the receiver is receiving.
答案2
得分: 2
你的mark
通道没有初始化[1],因此它永远不会准备好,因此在select
语句中永远不会被选择。否则,当多个case
同时可用时,select
语句会随机选择其中一个。
此外,你甚至没有在mark
上发送任何内容,但如果你尝试这样做,发送操作将被阻塞,因为无论如何都没有nil
通道的接收者。
如果没有准备好的case
,并且存在一个default
case,则选择默认情况,否则它会被阻塞。
请还要检查一下Go之旅。
[1] 使用var
声明将使mark
保持其零值,即nil
。
英文:
Your mark
channel is not initialized [1], so it will never be ready, therefore never chosen in the select
statement. Otherwise, when multiple case
s are available at the same time, the select
statement chooses one of them at random.
Furthermore you don't even send anything on mark
, but if you attempted that, the send operation would block, as there can't be any receiver for a nil
channel anyway.
If no case
is ready, and there is a default
case, the default is selected, otherwise it blocks.
Please check also the tour of Go.
<hr>
[1] a var
declaration will leave mark
to its zero value, which is nil
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论