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
posted @   -零  阅读(9487)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示