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之后)已经重构或简化了,这部分在后续会介绍。

旧版本的处理中包含一些改写:

  1. 当select没有任何case时,将永久阻塞goroutine。
  2. 当select只有一个case时,将被改写为if条件语句。
  3. 当select仅包含两个case,其中一个是default,将改写为if else条件语句。

流程

具体流程如下:

  1. 初始化阶段,决定case的比轮询顺序和加锁顺序。
  2. 查找是否有就绪的channel
  3. 将当前goroutine加入channel对应的收发队列并等待唤醒
  4. 被唤醒后找到满足的channel并处理
初始化

决定case的轮询顺序和加锁顺序。

其中轮询顺序在每次执行select时都会随机生成,之前说了,这样是为了避免饥饿。

加锁顺序的门道比较多,一点点解释:

首先select要首先获取所有case对应channel的锁,因为select想要同时等待多个channel,因此在select把自己挂进所有channel的等待队列之前,必须保证所有channel都是阻塞的。

其次是顺序问题。所有channel应当有固定的获取锁的顺序,即对于任意两个channel来说,select中获取两个channel锁的顺序必须是固定的,不然就会出现两个goroutine中各持有一部分锁互相等待的情况。

流程(新版本)