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
关键字。