Golang中的Select语句使用详解

Golang中的Select语句使用详解

package main

import (
    "fmt"
)

func g1(ch chan int) {
    ch <- 42
}

func g2(ch chan int) {
    ch <- 43
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go g1(ch1)
    go g1(ch2)

    select {
    case v1 := <-ch1:
        fmt.Println("Got: ", v1)
    case v2 := <-ch2:
        fmt.Println("Got: ", v2)
    default:
        fmt.Println("The default case!")
    }
}

为什么每次运行这段代码,我都会得到默认的答案?


更多关于Golang中的Select语句使用详解的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

谢谢。这是一个很有帮助的回答。

更多关于Golang中的Select语句使用详解的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


但如果我删除默认条件,它就能正常工作。这是为什么呢?

可能是因为到目前为止,还没有任何一个goroutine被调度以能够发送数据,尝试添加一些最小的休眠时间以强制重新调度到另一个goroutine。

@NobbZ 说得对。一个 Go 协程执行任何有价值任务所需的最短时间是 10 毫秒。尝试添加一些时间休眠。它会起作用的。

以下是两种情况下,在遇到 select 语句之前发生的事情:

  1. 准备通道
  2. 启动第一个 goroutine
  3. 启动第二个 goroutine

现在,对于包含 default 子句的情况:

  1. 目前 ch1ch2 都没有数据,因为其他 goroutine 还没有时间发送任何内容。
  2. default 分支被“选中”。

对于不包含 default 子句的情况:

  1. 目前 ch1ch2 都没有数据,因为其他 goroutine 还没有时间发送任何内容。
  2. 但是没有 default 分支,这个 goroutine 无法在未从任何通道接收到数据的情况下继续执行,因此它会让自己进入休眠状态,并将控制权交给调度器。
  3. 调度器随机地将控制权交给一个标记为“准备就绪”的 goroutine。
  4. 该 goroutine 会立即通过“它的”通道向主 goroutine 发送数据。
  5. 将控制权交还给调度器。
  6. 调度器知道主 goroutine 可以继续执行,但它仍然会在主 goroutine 和另一个待发送的 goroutine 之间随机选择。(因此,另一个发送者可能会也可能不会向通道中放入数据)
  7. 主 goroutine 直接或稍后获得了控制权,现在它从可用的通道中随机选择一个。

这是因为你的代码存在两个问题:通道操作和goroutine调用的不匹配。

首先,你创建了两个goroutine,但都调用了g1函数,这意味着ch2通道永远不会被写入数据。其次,select语句中的default分支会在所有通道都未就绪时立即执行,而goroutine的调度存在延迟。

以下是修正后的代码:

package main

import (
    "fmt"
    "time"
)

func g1(ch chan int) {
    ch <- 42
}

func g2(ch chan int) {
    ch <- 43
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go g1(ch1)
    go g2(ch2)  // 修正:调用g2而不是g1

    time.Sleep(time.Millisecond) // 确保goroutine已启动

    select {
    case v1 := <-ch1:
        fmt.Println("Got from ch1:", v1)
    case v2 := <-ch2:
        fmt.Println("Got from ch2:", v2)
    default:
        fmt.Println("The default case!")
    }
}

如果你希望避免使用time.Sleep,可以使用无缓冲通道的同步特性:

package main

import (
    "fmt"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        ch1 <- 42
    }()
    
    go func() {
        ch2 <- 43
    }()

    select {
    case v1 := <-ch1:
        fmt.Println("Got from ch1:", v1)
    case v2 := <-ch2:
        fmt.Println("Got from ch2:", v2)
    }
}

或者使用带缓冲的通道:

package main

import (
    "fmt"
)

func main() {
    ch1 := make(chan int, 1)
    ch2 := make(chan int, 1)
    
    ch1 <- 42
    ch2 <- 43

    select {
    case v1 := <-ch1:
        fmt.Println("Got from ch1:", v1)
    case v2 := <-ch2:
        fmt.Println("Got from ch2:", v2)
    default:
        fmt.Println("The default case!")
    }
}

在第一个修正版本中,time.Sleep确保了goroutine有足够时间启动并发送数据。在第二个版本中,移除default分支会使select阻塞直到某个通道就绪。第三个版本使用缓冲通道,数据已预先准备好,因此不会触发default分支。

回到顶部