在SELECT语句中,是首先使用通道中的缓冲区还是首先使用待处理队列的值?

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

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 &lt; ncases; i++ {
		casi = int(pollorder[i])
		cas = &amp;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 &gt; 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 &lt; 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 (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	var mark chan int
	length := 3
	c := make(chan int, length)
	for i := 1; i &lt;= length; i++ {
		c &lt;- i
	}

	go func() {
		c &lt;- 4
	}()

	time.Sleep(time.Millisecond * 10)
	select {
	case val := &lt;-c:
		fmt.Println(&quot;receive val is &quot;, val)
	case _ = &lt;-mark:
		fmt.Println(&quot;block chan&quot;)
	}
}

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(&quot;syncrecv: cas0=&quot;, cas0, &quot; c=&quot;, c, &quot;\n&quot;)
	}
	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&#39;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&#39;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 cases 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.

huangapple
  • 本文由 发表于 2021年6月19日 17:56:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/68045642.html
匿名

发表评论

匿名网友

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

确定