Go Select使用
Go Select使用
Go中的select和channel配合使用,通过select可以监听多个channel的I/O读写事件,当 IO操作发生时,触发相应的动作。
基本用法
1 2 3 4 5 6 7 8 | //select基本用法 select { case <- chan1: // 如果chan1成功读到数据,则进行该case处理语句 case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句 default : // 如果上面都没有成功,则进入default处理流程 |
使用规则
1 2 | 1.如果没有 default 分支, select 会阻塞在多个channel上,对多个channel的读/写事件进行监控。 2.如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有 default 分支,则执行 default 分支语句,如果连 default 都没有,则 select 语句会一直阻塞,直到至少有一个IO操作可以进行。 |
快速返回
同时监听不同的channel,做同一件工作,可以最快的返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package main import ( "fmt" "github.com/kirinlabs/HttpRequest" ) func main() { ch1 := make( chan int) ch2 := make( chan int) ch3 := make( chan int) go Getdata( "https://www.baidu.com" ,ch1) go Getdata( "https://www.baidu.com" ,ch2) go Getdata( "https://www.baidu.com" ,ch3) select { case v:=<- ch1: fmt.Println(v) case v:=<- ch2: fmt.Println(v) case v:=<- ch3: fmt.Println(v) } } func Getdata(url string,ch chan int){ req,err := HttpRequest.Get(url) if err != nil{ } else { ch <- req.StatusCode() } } |
随机返回
同时监控不同的channel,配上default,select也不会阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package main import ( "fmt" "github.com/kirinlabs/HttpRequest" ) func main() { ch1 := make( chan int) ch2 := make( chan int) ch3 := make( chan int) go func (){ for { Getdata( "https://www.baidu.com" , ch1) Getdata( "https://cn.bing.com" , ch2) Getdata( "https://cn.bing.com" , ch3) } }() go func (){ for { select { case v := <-ch1: fmt.Println( "信道1的结果:" ,v) case v := <-ch2: fmt.Println( "信道2的结果:" ,v) case v := <-ch3: fmt.Println( "信道3的结果:" ,v) default : continue } } }() select {} } func Getdata(url string,ch chan int){ req,err := HttpRequest.Get(url) if err != nil{ } else { ch <- req.StatusCode() } } |
通过select来检测channel的关闭事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | func TestSelect1() { start := time.Now() c := make( chan interface {}) go func () { time.Sleep(2*time.Second) close(c) }() fmt.Println( "Blocking on read..." ) select { case <-c: fmt.Printf( "Unblocked %v later.\n" , time.Since(start)) } } |
注意:当close channel时,读取channel的一方会从channel中读取到value,false,此时的value一般情况下为nil。
该例子也可以用来通知当不使用channel时,关闭channel的情况。
通过channel通知,从而退出死循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | func TestExitLoop() { done := make( chan interface {}) go func () { time.Sleep(2*time.Second) close(done) }() workCounter := 0 loop: for { select { case <-done: break loop default : } // Simulate work workCounter++ time.Sleep(1*time.Second) } fmt.Printf( "在通知退出循环时,执行了%d次.\n" , workCounter) } |
启动一个goroutine,该goroutine在2s后,关闭channel。此时,主协程会在select中的case <-done分支中得到通知,跳出死循环。而在此之前,会执行default分支的代码,这里是什么都不做。
超时机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package main import ( "fmt" "time" ) func main() { ch := make( chan int) quit := make( chan bool) //新开一个协程 go func () { for { select { case num := <-ch: fmt.Println( "num = " , num) case <-time.After(3 * time.Second): fmt.Println( "超时" ) quit <- true } } }() for i := 0; i < 5; i++ { ch <- i time.Sleep(time.Second) } <-quit fmt.Println( "程序结束" ) } |
死锁与默认情况
1 2 3 4 5 6 7 8 | package main func main() { ch := make( chan string) select { case <-ch: } } |
上面的程序中,我们在第 4 行创建了一个信道 ch
。我们在 select
内部(第 6 行),试图读取信道 ch。由于没有 Go 协程向该信道写入数据,因此 select
语句会一直阻塞,导致死锁。该程序会触发运行时 panic
,报错信息如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/tmp/sandbox416567824/main.go:6 +0x80
如果存在默认情况,就不会发生死锁,因为在没有其他 case 准备就绪时,会执行默认情况。我们用默认情况重写后,程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 | package main import "fmt" func main() { ch := make( chan string) select { case <-ch: default : fmt.Println( "default case executed" ) } } |
以上程序会输出:
default case executed
如果 select
只含有值为 nil
的信道,也同样会执行默认情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import "fmt" func main() { var ch chan string select { case v := <-ch: fmt.Println( "received value" , v) default : fmt.Println( "default case executed" ) } } |
在线运行程序
在上面程序中,ch
等于 nil
,而我们试图在 select
中读取 ch
(第 8 行)。如果没有默认情况,select
会一直阻塞,导致死锁。由于我们在 select
内部加入了默认情况,程序会执行它,并输出:
default case executed
空 select
1 2 3 4 | package main func main() { select {} } |
我们已经知道,除非有 case
执行,select
语句就会一直阻塞着。在这里,select
语句没有任何 case
,因此它会一直阻塞,导致死锁。该程序会触发 panic
,输出如下:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:
main.main()
/tmp/sandbox299546399/main.go:4 +0x20
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?