MST

星途 面试题库

面试题:Go语言中select语句在避免死锁方面的应用及陷阱

在Go语言中,`select`语句常被用于处理多个通道的操作。请说明`select`语句在避免死锁方面的原理和作用。同时,列举至少两个在使用`select`语句时可能会导致死锁的陷阱场景,并解释如何避免这些陷阱。可以结合实际代码示例进行说明。
12.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

select 语句避免死锁的原理和作用

  1. 原理select 语句会阻塞,直到其内部的某个 case 语句可以执行。它会同时等待多个通道操作(发送或接收)准备就绪。如果多个 case 都准备就绪,会随机选择其中一个执行。当没有任何 case 可以执行,并且没有 default 分支时,select 语句就会一直阻塞,这避免了因为通道操作无法立即完成而导致的死锁。因为程序不会继续执行可能导致死锁的后续代码,而是等待直到通道操作可以进行。
  2. 作用select 语句主要用于在多个通道之间进行多路复用,协调不同的并发操作。它允许程序优雅地处理多个异步事件,确保在处理通道操作时不会因为资源竞争或等待而陷入死锁状态,从而提高程序的并发性能和稳定性。

可能导致死锁的陷阱场景及避免方法

场景一:无 default 分支且所有 case 阻塞

package main

import (
    "fmt"
)

func main() {
    var ch1 chan int
    var ch2 chan int
    select {
    case <-ch1:
        fmt.Println("Received from ch1")
    case <-ch2:
        fmt.Println("Received from ch2")
    }
}

分析:在这个例子中,ch1ch2 都是未初始化的通道,因此两个 case 都会阻塞。由于没有 default 分支,select 语句会永远阻塞,导致死锁。

避免方法:添加 default 分支,或者确保至少有一个通道已经准备好进行操作。

package main

import (
    "fmt"
)

func main() {
    var ch1 chan int
    var ch2 chan int
    select {
    case <-ch1:
        fmt.Println("Received from ch1")
    case <-ch2:
        fmt.Println("Received from ch2")
    default:
        fmt.Println("No channel is ready")
    }
}

场景二:所有 case 都在同一个 goroutine 中发送和接收

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    select {
    case ch <- 1:
        fmt.Println("Sent 1 to ch")
    case val := <-ch:
        fmt.Println("Received", val, "from ch")
    }
}

分析:在这个例子中,select 语句在同一个 goroutine 中尝试向通道 ch 发送数据和从通道 ch 接收数据。如果先执行发送操作,它会阻塞等待另一个 goroutine 来接收;如果先执行接收操作,它会阻塞等待另一个 goroutine 来发送,这就导致了死锁。

避免方法:将发送和接收操作放在不同的 goroutine 中。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    go func() {
        ch <- 1
    }()
    select {
    case val := <-ch:
        fmt.Println("Received", val, "from ch")
    }
}