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
但如果我删除默认条件,它就能正常工作。这是为什么呢?
可能是因为到目前为止,还没有任何一个goroutine被调度以能够发送数据,尝试添加一些最小的休眠时间以强制重新调度到另一个goroutine。
@NobbZ 说得对。一个 Go 协程执行任何有价值任务所需的最短时间是 10 毫秒。尝试添加一些时间休眠。它会起作用的。
以下是两种情况下,在遇到 select 语句之前发生的事情:
- 准备通道
- 启动第一个 goroutine
- 启动第二个 goroutine
现在,对于包含 default 子句的情况:
- 目前
ch1和ch2都没有数据,因为其他 goroutine 还没有时间发送任何内容。 default分支被“选中”。
对于不包含 default 子句的情况:
- 目前
ch1和ch2都没有数据,因为其他 goroutine 还没有时间发送任何内容。 - 但是没有 default 分支,这个 goroutine 无法在未从任何通道接收到数据的情况下继续执行,因此它会让自己进入休眠状态,并将控制权交给调度器。
- 调度器随机地将控制权交给一个标记为“准备就绪”的 goroutine。
- 该 goroutine 会立即通过“它的”通道向主 goroutine 发送数据。
- 将控制权交还给调度器。
- 调度器知道主 goroutine 可以继续执行,但它仍然会在主 goroutine 和另一个待发送的 goroutine 之间随机选择。(因此,另一个发送者可能会也可能不会向通道中放入数据)
- 主 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分支。

