Golang select 关键字
最后修改于 2025 年 5 月 7 日
本教程将解释如何在 Go 中使用 select 关键字。我们将通过并发模式的实际示例来介绍通道操作基础知识。
select 语句允许一个 goroutine 等待多个通信操作。如果多个 case 都准备就绪,它会随机选择一个。
在 Go 中,select 对于 goroutine 之间的协调至关重要。它处理通道的发送/接收,并实现超时和非阻塞操作。
带两个通道的基本 select
此示例展示了 select 与两个通道的最简单用法。它等待来自任一通道的数据。
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "from ch1"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "from ch2"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
}
}
}
select 会等待任一通道有数据。由于 ch1 更快,其消息会先打印。循环确保两个消息都能接收到。
带 default case 的 select
添加 default case 使 select 变为非阻塞。此示例演示了在没有通道就绪时立即执行。
package main
import "fmt"
func main() {
ch := make(chan string)
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No message received")
}
}
由于通道为空且没有 goroutine 发送,default case 会立即执行。这种模式对于轮询通道很有用。
带超时的 select
在 select 中使用 time.After 来实现超时。这可以防止通道操作无限期阻塞。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "result"
}()
select {
case res := <-ch:
fmt.Println(res)
case <-time.After(2 * time.Second):
fmt.Println("timeout")
}
}
超时 case 在 2 秒后触发,而 goroutine 需要 3 秒。这确保了程序不会无限期地等待慢速操作。
用于发送和接收的 select
Select 可以处理发送和接收操作。此示例演示了不同通道方向之间的协调。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
done := make(chan bool)
go func() {
for {
select {
case ch <- 1:
fmt.Println("Sent 1")
case <-done:
fmt.Println("Stopping")
return
}
}
}()
go func() {
time.Sleep(2 * time.Second)
done <- true
}()
for i := 0; i < 5; i++ {
fmt.Println("Received:", <-ch)
time.Sleep(300 * time.Millisecond)
}
}
第一个 goroutine 在发送值和检查 done 通道之间交替。2 秒后,done 通道会停止发送者。
带多个就绪 case 的 select
当多个 case 就绪时,select 会随机选择一个。此示例展示了非确定性行为。
package main
import "fmt"
func main() {
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch1 <- "one"
ch2 <- "two"
for i := 0; i < 10; i++ {
select {
case msg := <-ch1:
fmt.Println(msg)
ch1 <- "one"
case msg := <-ch2:
fmt.Println(msg)
ch2 <- "two"
}
}
}
两个通道都已就绪,因此 select 会在它们之间随机选择。输出显示了“one”和“two”的混合,顺序不可预测。
带 nil 通道的 select
Select 会忽略 nil 通道。此示例演示了如何使用 nil 来动态禁用 case。
package main
import (
"fmt"
"time"
)
func main() {
var ch chan string
go func() {
time.Sleep(2 * time.Second)
ch = make(chan string, 1)
ch <- "message"
}()
for {
select {
case msg := <-ch:
fmt.Println("Received:", msg)
return
default:
fmt.Println("No channel yet")
time.Sleep(500 * time.Millisecond)
}
}
}
最初 ch 是 nil,因此只有 default case 会执行。2 秒后,ch 变为活动状态,可以接收消息。
实际示例:Worker Pool
这个实际示例展示了如何在 worker pool 模式中使用 select 来协调多个 goroutine。
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("worker %d started job %d\n", id, j)
time.Sleep(time.Second)
fmt.Printf("worker %d finished job %d\n", id, j)
results <- j * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
for a := 1; a <= 5; a++ {
select {
case res := <-results:
fmt.Println("result:", res)
case <-time.After(2 * time.Second):
fmt.Println("timeout waiting for result")
}
}
}
select 会等待 worker 的结果,并带有超时。它确保程序不会在 worker 未能完成其任务时挂起。
来源
本教程通过并发编程中通道操作的实际示例,讲解了 Go 中的 select 关键字。