golang_select
Error: Invalid Frontmatter
Path: /var/www/grav/user/pages/02.blog/05.golang_select/blog.md
Failed to read /var/www/grav/user/pages/02.blog/05.golang_select/blog.md: You cannot define a sequence item when in a mapping at line 7 (near "- 学习笔记")
---
title: 'golang select 学习笔记'
taxonomy:
category:
- 技术
tag:
- golang
- 学习笔记
---
select很类似于switch,但select必须对应channel的收发操作。举一个简单的例子:
```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 有两大特性,分别是非阻塞式的收发和随机执行。
select 解决什么问题?
select最主要解决两大问题:同时对多个管道进行管理和非阻塞式收发。
两大特性
非阻塞式收发
非阻塞式收发的关键是default,default可以被看做一个无条件的case。当没有case被满足时,将执行default部分。defualt可省略,如果select没有default,则会在没有任何case满足时阻塞。
随机执行
随机执行是指在多个case同时满足的时候,随机执行其中一个。没有固定的顺序,避免出现饥饿问题。
select的原理
优化(旧版本)
select中本身存在一些特殊情况的优化,目前版本(1.22之后)已经重构或简化了,这部分在后续会介绍。
旧版本的处理中包含一些改写:
- 当select没有任何case时,将永久阻塞goroutine。
- 当select只有一个case时,将被改写为if条件语句。
- 当select仅包含两个case,其中一个是default,将改写为if else条件语句。
流程
具体流程如下:
- 初始化阶段,决定case的比轮询顺序和加锁顺序。
- 查找是否有就绪的channel
- 将当前goroutine加入channel对应的收发队列并等待唤醒
- 被唤醒后找到满足的channel并处理
初始化
决定case的轮询顺序和加锁顺序。
其中轮询顺序在每次执行select时都会随机生成,之前说了,这样是为了避免饥饿。
加锁顺序的门道比较多,一点点解释:
首先select要首先获取所有case对应channel的锁,因为select想要同时等待多个channel,因此在select把自己挂进所有channel的等待队列之前,必须保证所有channel都是阻塞的。
其次是顺序问题。所有channel应当有固定的获取锁的顺序,即对于任意两个channel来说,select中获取两个channel锁的顺序必须是固定的,不然就会出现两个goroutine中各持有一部分锁互相等待的情况。
流程(新版本)