select - 选择中同时发生多个事件时的预期行为

标签 select go channel goroutine

假设一个 goroutine 在两个 unbuffered channel onetwo 上等待以下选择

select {
    case <-one:
        fmt.Println("read from one")
    case <-two:
        fmt.Println("read from two")
}

一个一个的goroutine正在等待下面的发送

one <- 1

另一个正在等待下面

two <- 2

第一个等待选择意味着缓冲区中有空间用于 channel onetwo,然后是 select 情况保证运行?它是确定性的还是可以在一个 channel 最后留下一个未读值的情况下运行。

如果只有一个保证的净输出,那么 select 是否确保参与 select 的所有 channel 上的所有操作的总顺序?这看起来效率很低..


例如在下面的代码中

package main

import (
    "fmt"
    "time"
    "sync"
)

func main() {
    one_net := 0
    two_net := 0
    var mtx = &sync.Mutex{}
    for i := 0; i < 8; i++ {
        one, two := make(chan int), make(chan int)
        go func() { // go routine one
            select {
            case <-one:
                fmt.Println("read from one")

                mtx.Lock()
                one_net++
                mtx.Unlock()
            case <-two:
                fmt.Println("read from two")

                mtx.Lock()
                two_net++
                mtx.Unlock()
            }
        }()
        go func() { // go routine two
            one <- 1

            mtx.Lock()
            one_net--
            mtx.Unlock()

            fmt.Println("Wrote to one")
        }()
        go func() { // go routine three
            two <- 2

            mtx.Lock()
            two_net--
            mtx.Unlock()

            fmt.Println("Wrote to two")
        }()
        time.Sleep(time.Millisecond)
    }
    mtx.Lock()
    fmt.Println("one_net", one_net)
    fmt.Println("two_net", two_net)
    mtx.Unlock()
}

读取次数与写入次数是否会不匹配(即 one_nettwo_net 最后是否可以为非 0)?例如,如果 select 语句正在等待来自两个 channel 的读取,然后 goroutines 2 和 3 通过它们各自的写入,但是 select 只接受其中一个写入.

最佳答案

The Go Programming Language Specification

Select statements

A "select" statement chooses which of a set of possible send or receive operations will proceed.

If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.

您的问题不准确:How to create a Minimal, Complete, and Verifiable example.例如,

chan.go:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println()
    for i := 0; i < 8; i++ {
        one, two := make(chan int), make(chan int)
        go func() { // goroutine one
            select {
            case <-one:
                fmt.Println("read from one")
            case <-two:
                fmt.Println("read from two")
            }
            select {
            case <-one:
                fmt.Println("read from one")
            case <-two:
                fmt.Println("read from two")
            }
            fmt.Println()
        }()
        go func() { // goroutine two
            one <- 1
        }()
        go func() { // goroutine three
            two <- 2
        }()
        time.Sleep(time.Millisecond)
    }
}

输出:

$ go run chan.go

read from two
read from one

read from one
read from two

read from one
read from two

read from two
read from one

read from one
read from two

read from two
read from one

read from one
read from two

read from two
read from one

$

您期望什么行为,为什么?


The Go Programming Language Specification

Channel types

A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type.

A new, initialized channel value can be made using the built-in function make, which takes the channel type and an optional capacity as arguments:

make(chan int, 100)

The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives). A nil channel is never ready for communication.

Go statements

A "go" statement starts the execution of a function call as an independent concurrent thread of control, or goroutine, within the same address space.

The function value and parameters are evaluated as usual in the calling goroutine, but unlike with a regular call, program execution does not wait for the invoked function to complete. Instead, the function begins executing independently in a new goroutine. When the function terminates, its goroutine also terminates. If the function has any return values, they are discarded when the function completes.

分析您的新示例:

channel 是无缓冲的。 Goroutine 2 和 3 在 Goroutine 1 上等待。无缓冲 channel 上的发送会一直等待,直到有待处理的接收。当 goroutine one select 被评估时, channel 1 或 channel 2 上将有一个待处理的接收。在该 channel 上发送的两个或三个 goroutine 现在可以发送和终止。 Goroutine one 现在可以在该 channel 上执行接收并终止。作为一种粗略的 goroutine 同步机制,我们等待 goroutine main 一毫秒然后终止它,这会终止任何其他 goroutine。它将终止两个或三个未发送的 goroutine,因为它仍在等待挂起的接收。

您会问“读取次数与写入次数是否会不匹配(即 one_net 和 two_net 最后是否可以为非 0)?例如,在 select 语句等待读取的情况下来自两个 channel ,然后 goroutines 2 和 3 通过它们各自的写入,但随后 select 只选择其中一个写入。”

只有 goroutines 2 和 3 之一可以发送(写入)。将恰好有一个(发送)写入和一个(接收)读取。这假设 goroutine main 在这发生之前没有终止,也就是说,它在一毫秒内发生。

关于select - 选择中同时发生多个事件时的预期行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44745406/

相关文章:

使用子查询更新 MySQL

javascript - 需要帮助在循环中将多个数组值设置为 null - javascript

unix - go语言os.FileMode函数是如何从integers/octal/转换权限的???在设置标志之前?

java - Go channel vs Java BlockingQueue

java - 为什么我会收到 CancelledKeyException?

mysql - 无法从选择中更新

sql - 非数字列中的SQL MAX函数

json - 如何在 Go 中处理 JSON 动态键

mongodb - Mgo 是否缓存连接字符串?

go - 在需要时中断 anaconda 的推特流