理解 Go 语言中的 select 用法

简介: 本文深入解析了Go语言中`select`的用法,它类似于`switch case`,但仅用于通道(channel)的操作。文章通过多个示例说明了`select`的基本用法、避免死锁的方法、随机性特点以及如何实现超时机制。同时总结了`select`与`switch`的区别:`select`专用于通道操作,case执行是随机的,需注意死锁问题,且不支持`fallthrough`和函数表达式。

今天深入了解下Go语言中select的用法,和switch case很类似,用法比较单一,它仅能用于 信道/通道 的相关操作,每个case会对应一个通道的通信(接收或发送)过程。select会一直等待,直到某个case的通信操作完成时,就会执行case分支对应的语句

go

代码解读

复制代码

//语法如下:
select{
    case <-ch1:
        ...
    case data := <-ch2:
        ...
    case ch3<-data:
        ...
    default:
        默认操作
}

接下来,我们来看几个例子帮助理解这个 select 的模型。

1、最简单的例子

go

代码解读

复制代码

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    c2 <- "hello"

    select {
    case msg1 := <-c1:
      fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
      fmt.Println("c2 received: ", msg2)
    default:
      fmt.Println("No data received.")
    }
}

在运行 select 时,会遍历所有)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下c2 received:  hello,c2接收到数据了

2. 避免造成死锁

select 在执行过程中,必须命中其中的某一分支。如果在遍历完所有的 case 后,若没有命中任何一个 case 表达式,就会进入 default 里的代码分支。

但刚好这时你没有写 default 分支,会发生什么呢?这时select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误,就像下面这样子。

go

代码解读

复制代码

package main
import (
    "fmt"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    // c2 <- "hello"
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
        // default:
        //     fmt.Println("No data received.")
    }
}

运行后输出如下

go

代码解读

复制代码

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select]:
nginxlog/tools.OutPrintf()
	D:/work/perlearn/go_space/total/tools/parasenginx.go:233 +0xc5
main.main()
	D:/work/perlearn/go_space/total/main.go:8 +0x17
Process finished with exit code 2

3、解决这个问题的方法有两种

一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。这时再执行上面代码就不会deadlock

go

代码解读

复制代码

package main

import (
    "fmt"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
  // c2 <- "hello"
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    default:
    }
}

另一个是,确保其中某一个信道可以接收到数据

go

代码解读

复制代码

package main

import (
    "fmt"
    "time"
)
func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
  // 开启一个协程,可以发送数据到信道
    go func() {
        time.Sleep(time.Second * 1)
        c2 <- "hello"
    }()
    select {
    case msg1 := <-c1:
        fmt.Println("c1 received: ", msg1)
    case msg2 := <-c2:
        fmt.Println("c2 received: ", msg2)
    }
}

4、select 随机性

之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。

go

代码解读

复制代码

func main(){
    c1 := make(chan string, 1)
	c2 := make(chan string, 1)
	c1 <- "hello"
	c2 <- "123123"
	select {
	case msg1 := <-c1:
		fmt.Println("c1 received: ", msg1)
	case msg2 := <-c2:
		fmt.Println("c2 received: ", msg2)
	}
}

5. select 的超时

当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间

go

代码解读

复制代码

func main(){
	ch1:=make(chan string,1)
	ch2:=make(chan string,1)
	timeout:=make(chan bool,1)
	go func(ch chan bool, t int) {
		time.Sleep(time.Second * time.Duration(t))
		ch <- true
	}(timeout,2)
	select {
	case msg:=<-ch1:
		fmt.Println("ch1 recvied data",msg)
	case ms2:=<-ch2:
		fmt.Println("ch2 recvied data",ms2)
	case <-timeout:
		fmt.Println("Timeout, exit.")
	}
}

输出如下

text

代码解读

复制代码

Timeout, exit.

6、 总结一下

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  • select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
  • select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  • select 要注意避免出现死锁,同时也可以自行实现超时机制;
  • select 里没有类似 switch 里的 fallthrough 的用法;
  • select 不能像 switch 一样接函数或其他表达式。


转载来源:https://juejin.cn/post/7047778400404144159


相关文章
|
供应链 Go
掌握Go语言:利用Go语言的单向通道和select语句,提升库存管理效率(21)
掌握Go语言:利用Go语言的单向通道和select语句,提升库存管理效率(21)
128 0
|
4月前
|
人工智能 监控 Kubernetes
Go语言在select语句中实现优先级
Go语言中的`select`语句用于监控多个Channel的发送或接收操作,选择就绪的分支执行。它支持多种使用场景,如空`select`永久阻塞、单`case`阻塞读写、多`case`随机选择、配合`default`实现非阻塞操作等。通过嵌套`select`还可实现执行优先级,适用于如Kubernetes中任务调度等实际场景。
|
程序员 Go
Golang深入浅出之-Select语句在Go并发编程中的应用
【4月更文挑战第23天】Go语言中的`select`语句是并发编程的关键,用于协调多个通道的读写。它会阻塞直到某个通道操作可行,执行对应的代码块。常见问题包括忘记初始化通道、死锁和忽视`default`分支。要解决这些问题,需确保通道初始化、避免死锁并添加`default`分支以处理无数据可用的情况。理解并妥善处理这些问题能帮助编写更高效、健壮的并发程序。结合使用`context.Context`和定时器等工具,可提升`select`的灵活性和可控性。
232 2
|
2月前
|
Go 开发者
Go语言实战案例:使用select监听多个channel
本文为《Go语言100个实战案例 · 网络与并发篇》第5篇,详解Go并发核心工具`select`的使用。通过实际案例讲解如何监听多个Channel、实现多任务处理、超时控制和非阻塞通信,帮助开发者掌握Go并发编程中的多路异步事件处理技巧。
|
4月前
|
监控 Go 开发者
深入解析 go 语言中的 select 语句
在 Go 语言中,`select` 是专为并发编程设计的控制结构,用于在多个 channel 操作间进行非阻塞选择。它类似于 `switch`,但所有 case 分支必须是 channel 的 I/O 操作。本文通过通俗易懂的语言和丰富代码示例,详细讲解 `select` 的各种用法,包括基本语法、空 `select`、多分支选择、`default` 分支、特点总结及最佳实践,适合初学者和有经验的开发者学习参考。掌握 `select`,能有效提升 Go 并发编程的灵活性与效率。
161 6
|
4月前
|
Go 调度 开发者
Go 并发编程基础:select 多路复用
Go 语言中的 `select` 是一种强大的并发控制结构,用于同时监听多个通道操作。它支持随机选择就绪的通道、阻塞等待以及通过 `default` 实现非阻塞通信。结合 `time.After()`,可轻松实现超时机制,适用于网络请求、任务调度等场景。本文详细介绍了 `select` 的基本用法、特性及实战技巧,如合并多通道输入、处理通道关闭等,帮助开发者高效管理协程与通道交互,避免常见陷阱。
|
Go
go select 使用总结
go select 使用总结
130 1
|
Go
Go并发编程:玩转select语句
Go并发编程:玩转select语句
104 0
Go并发编程:玩转select语句
go 缓冲区循环 以及 select选择
go 缓冲区循环 以及 select选择
109 0

热门文章

最新文章

下一篇
oss教程