Golang中通过select语句监听多个channel,实现并发控制、超时与非阻塞操作,并利用done channel或context.Context优雅关闭goroutine。

在Golang中,要同时监听多个channel的事件,我们主要依赖
select
select
switch
case
select
case
case
case
一个最基础的
select
package main
import (
"fmt"
"time"
)
func worker(id int, ch chan string) {
for {
select {
case msg := <-ch:
fmt.Printf("Worker %d received: %s\n", id, msg)
case <-time.After(2 * time.Second):
fmt.Printf("Worker %d timed out waiting for message.\n", id)
return // 退出goroutine
}
}
}
func main() {
messageChan1 := make(chan string)
messageChan2 := make(chan string)
go worker(1, messageChan1)
go worker(2, messageChan2) // 实际上这里worker 2并不会收到消息,因为main只发给了messageChan1
go func() {
for i := 0; i < 5; i++ {
time.Sleep(500 * time.Millisecond)
messageChan1 <- fmt.Sprintf("Hello from main %d", i)
}
close(messageChan1) // 发送完毕后关闭channel
}()
// 给goroutines一些时间运行
time.Sleep(5 * time.Second)
fmt.Println("Main finished.")
}在这个例子中,
worker
ch
time.After
select
case
case
select
case
default
default
select
立即学习“go语言免费学习笔记(深入)”;
这是
select
select
case
case
举个例子,假设你有两个channel
ch1
ch2
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
ch1 <- "From ch1"
}()
go func() {
time.Sleep(100 * time.Millisecond)
ch2 <- "From ch2"
}()
// 尝试多次运行,你会发现输出结果可能是 "Received From ch1" 也可能是 "Received From ch2"
select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
}
time.Sleep(1 * time.Second) // 等待一下,确保goroutine有机会执行
}多次运行这段代码,你会发现输出结果是不确定的,有时是
Received From ch1
Received From ch2
select
select
实现超时控制: 超时控制通常用于限制一个操作的等待时间。在
select
time.After
time.After(duration)
duration
package main
import (
"fmt"
"time"
)
func fetchResource(timeout time.Duration) (string, error) {
dataChan := make(chan string)
errChan := make(chan error)
go func() {
// 模拟一个可能耗时的网络请求或计算
time.Sleep(timeout / 2) // 假设这个操作通常很快完成
// time.Sleep(timeout * 2) // 模拟一个会超时的操作
dataChan <- "Resource data loaded successfully!"
// errChan <- fmt.Errorf("failed to load resource") // 也可以发送错误
}()
select {
case data := <-dataChan:
return data, nil
case err := <-errChan:
return "", err
case <-time.After(timeout): // 超时控制
return "", fmt.Errorf("operation timed out after %v", timeout)
}
}
func main() {
fmt.Println("Attempting to fetch resource with 1 second timeout...")
data, err := fetchResource(1 * time.Second)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Success:", data)
}
fmt.Println("\nAttempting to fetch resource with 100 millisecond timeout (likely to timeout)...")
data, err = fetchResource(100 * time.Millisecond)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Success:", data)
}
}在这个例子中,
fetchResource
select
dataChan
errChan
time.After
实现非阻塞操作: 非阻塞操作意味着如果某个channel操作当前无法完成,程序不应该等待,而是立即执行其他逻辑。这可以通过
select
default
select
case
default
default
select
package main
import (
"fmt"
"time"
)
func tryReceive(ch chan string) {
select {
case msg := <-ch:
fmt.Println("Received message:", msg)
default:
fmt.Println("No message available, continuing...")
}
}
func main() {
myChan := make(chan string, 1) // 创建一个带缓冲的channel
fmt.Println("First attempt (channel is empty):")
tryReceive(myChan) // 此时ch为空,default会执行
myChan <- "Hello Go!" // 发送一个消息到channel
fmt.Println("\nSecond attempt (channel has message):")
tryReceive(myChan) // 此时ch有消息,会接收并打印
fmt.Println("\nThird attempt (channel is empty again):")
tryReceive(myChan) // 此时ch又空了,default会执行
// 如果没有default,第一次和第三次调用会一直阻塞
// 只有当channel有消息时才会解除阻塞
time.Sleep(100 * time.Millisecond) // 稍微等待一下,确保输出顺序
}default
select
default
优雅地关闭通过
select
context.Context
使用“完成”Channel: 这是最直接的方式,给每个工作goroutine一个额外的channel,当需要关闭时,向这个channel发送一个信号。
package main
import (
"fmt"
"time"
)
func workerWithDone(id int, dataChan <-chan string, done <-chan struct{}) {
fmt.Printf("Worker %d started.\n", id)
for {
select {
case data, ok := <-dataChan:
if !ok { // dataChan被关闭
fmt.Printf("Worker %d: Data channel closed, exiting.\n", id)
return
}
fmt.Printf("Worker %d received: %s\n", id, data)
case <-done: // 收到关闭信号
fmt.Printf("Worker %d received done signal, exiting gracefully.\n", id)
return
}
}
}
func main() {
dataQueue := make(chan string, 5)
doneChan := make(chan struct{}) // 用于发送关闭信号的channel
go workerWithDone(1, dataQueue, doneChan)
// 主goroutine发送一些数据
for i := 0; i < 10; i++ {
dataQueue <- fmt.Sprintf("Task-%d", i+1)
time.Sleep(100 * time.Millisecond)
}
// 模拟工作一段时间后,发送关闭信号
fmt.Println("Main: Signaling worker to stop...")
close(doneChan) // 关闭doneChan,所有监听它的goroutine都会收到信号
// 也可以通过向doneChan发送一个空结构体来通知,但关闭channel是更常见的模式
// doneChan <- struct{}{}
// 等待worker goroutine有机会退出
time.Sleep(500 * time.Millisecond)
fmt.Println("Main: All done.")
}这里,
workerWithDone
dataChan
done
main
doneChan
workerWithDone
case <-done:
dataChan
data, ok := <-dataChan
ok=false
dataChan
done
done
使用context.Context
context.Context
context.Context
Done()
package main
import (
"context"
"fmt"
"time"
)
func workerWithContext(ctx context.Context, dataChan <-chan string) {
fmt.Println("Worker started with context.")
for {
select {
case data, ok := <-dataChan:
if !ok {
fmt.Println("Worker: Data channel closed, exiting.")
return
}
fmt.Printf("Worker received: %s\n", data)
case <-ctx.Done(): // 监听context的取消信号
fmt.Println("Worker received context cancellation, exiting gracefully.")
// 可以在这里进行一些清理工作
return
}
}
}
func main() {
dataQueue := make(chan string, 5)
ctx, cancel := context.WithCancel(context.Background()) // 创建一个可取消的context
go workerWithContext(ctx, dataQueue)
// 主goroutine发送一些数据
for i := 0; i < 10; i++ {
dataQueue <- fmt.Sprintf("Task-%d", i+1)
time.Sleep(100 * time.Millisecond)
}
// 模拟工作一段时间后,取消context
fmt.Println("Main: Canceling context to stop worker...")
cancel() // 发送取消信号
// 等待worker goroutine有机会退出
time.Sleep(500 * time.Millisecond)
fmt.Println("Main: All done.")
}context.WithCancel
Context
cancel
cancel()
ctx.Done()
workerWithContext
case <-ctx.Done():
无论是使用独立的
done
context.Context
以上就是如何使用Golang的select语句监听多个channel的事件的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号