switch和select

switchselect在语法上看起来很相似,但它们是为解决完全不同的问题而设计的。

简单来说:

  • switch值(Value) 的选择。
  • select通道(Channel) 的选择。

你可以用一个比喻来理解:

  • switch 就像一个路口,你手里拿着一张地图(一个变量的值),根据地图上的指示(case匹配的值),选择走哪条路。这是一个确定的、基于已有信息的决定。
  • select 就像一个公交车站,有多路公交车(多个channel)。你不知道哪一辆会先到,但你会等待,并搭乘最先到达的那一辆。这是一个不确定的、基于未来事件的决定。

下面我们来深入探讨它们的细节、特性和用法。


switch 关键字:值的选择器

switch 是一个条件分支语句,它将一个表达式的值与一系列case子句进行匹配,并执行匹配的那个分支。它是if-else if-else链条的更清晰、更强大的替代品。

关键特性:

  1. 隐式break:Go的switch在每个case执行完毕后会自动跳出,不需要像C/C++那样手动写break。这避免了很多常见的错误。

  2. fallthrough关键字:如果你确实需要执行下一个case的代码块(不进行条件判断),可以显式使用fallthrough。这在实际中用得很少。

  3. 一个case可以匹配多个值:用逗号分隔即可。

  4. 无表达式的switchswitch后面可以不带任何表达式,此时它等价于switch true,可以让你编写更清晰的if-else if-else逻辑。

  5. 类型选择 (Type Switch):这是switch一个非常强大的特性,专门用于判断一个接口变量(interface{})中实际存储的是哪种类型。

代码示例:

1. 基本用法

1
2
3
4
5
6
7
8
9
day := "Monday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("It's a weekday")
case "Saturday", "Sunday":
fmt.Println("It's the weekend!")
default:
fmt.Println("Not a valid day.")
}

2. 无表达式的switch (更清晰的 if-else)

1
2
3
4
5
6
7
8
9
10
11
score := 85
switch { // 等价于 switch true
case score >= 90:
fmt.Println("Grade: A")
case score >= 80:
fmt.Println("Grade: B")
case score >= 70:
fmt.Println("Grade: C")
default:
fmt.Println("Grade: D")
}

3. 类型选择 (Type Switch)

1
2
3
4
5
6
7
8
9
10
11
12
13
var i interface{} = "hello"

switch v := i.(type) {
case int:
fmt.Printf("It's an int: %d\n", v)
case string:
fmt.Printf("It's a string: %s\n", v)
case bool:
fmt.Printf("It's a bool: %t\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
// 输出: It's a string: hello

select 关键字:通道的调度器

select 专门用于处理并发和通道操作。它会等待多个通道操作中的一个准备就绪,然后执行其对应的case代码块。

关键特性:

  1. 阻塞性:如果select中所有的case后面的通道操作都不能立即执行(比如接收空通道,或发送给满通道),select将会阻塞,直到其中一个可以执行。

  2. 随机选择:如果多个通道操作同时准备就绪,select伪随机地选择一个来执行。这保证了公平性,防止某个通道被“饿死”。

  3. default子句:如果select中包含一个default子句,那么select永远不会阻塞。它会立即检查所有通道,如果有准备就绪的就执行,如果没有,就立即执行default子句。这常用于实现非阻塞的通道操作

  4. 超时控制selecttime.After结合是实现操作超时的经典模式。

代码示例:

1. 基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
time.Sleep(2 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "two"
}()

// select会等待ch1和ch2
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("received", msg1)
case msg2 := <-ch2:
fmt.Println("received", msg2)
}
}
// 输出顺序:
// received two (ch2先就绪)
// received one (ch1后就绪)

2. default实现非阻塞接收

1
2
3
4
5
6
7
8
9
messages := make(chan string)
// ... 可能有也可能没有goroutine向messages发送数据

select {
case msg := <-messages:
fmt.Println("received message", msg)
default:
fmt.Println("no message received, continue.")
}

3. 超时模式

1
2
3
4
5
6
7
8
9
10
11
12
13
c1 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c1 <- "result 1"
}()

select {
case res := <-c1:
fmt.Println(res)
case <-time.After(1 * time.Second): // time.After返回一个channel
fmt.Println("timeout 1")
}
// 输出: timeout 1 (因为1秒的超时先于2秒的操作完成)

核心区别总结

特性 switch select
核心目的 进行分支选择 通道操作 进行并发调度
操作对象 变量、常量、表达式的值、类型 chan 的发送 ch <- v 或接收 <- ch
求值方式 从上到下,顺序匹配case 同时评估所有case,等待一个就绪
执行逻辑 匹配第一个符合条件的case 执行第一个准备就绪case(若多个就绪则随机选一)
default行为 如果所有case都不匹配,则执行default 如果所有case都未就绪,则执行default(实现非阻塞)
是否阻塞 永不阻塞 会阻塞,直到有case就绪(除非有default
主要应用场景 替代if-else、状态机、类型判断 并发编程、goroutine协调、超时控制、多路复用
Author

Cofeesy

Posted on

2025-09-04

Updated on

2025-09-04

Licensed under

Comments