面试题答案
一键面试同步原语相关
- 互斥锁死锁:
- 当多个Goroutine相互等待对方释放已经持有的互斥锁时,就会发生死锁。例如,Goroutine A获取了互斥锁
mu1
,然后试图获取mu2
,而Goroutine B获取了mu2
,同时又试图获取mu1
,这种情况下两个Goroutine都会卡住等待对方释放锁,从而导致死锁。
var mu1, mu2 sync.Mutex go func() { mu1.Lock() defer mu1.Unlock() mu2.Lock() defer mu2.Unlock() }() go func() { mu2.Lock() defer mu2.Unlock() mu1.Lock() defer mu1.Unlock() }()
- 当多个Goroutine相互等待对方释放已经持有的互斥锁时,就会发生死锁。例如,Goroutine A获取了互斥锁
- 条件变量使用不当:
- 如果在使用
sync.Cond
时,没有正确的在条件变量等待前获取相关的互斥锁,或者在等待后没有重新获取锁,可能导致Goroutine卡住。另外,如果没有其他Goroutine来唤醒等待在条件变量上的Goroutine,也会造成该Goroutine一直处于等待状态。
var mu sync.Mutex cond := sync.NewCond(&mu) go func() { // 未获取锁就等待,错误做法 cond.Wait() }()
- 如果在使用
Channel使用相关
- 无缓冲Channel收发不匹配:
- 对于无缓冲Channel,发送操作会阻塞直到有其他Goroutine接收数据,接收操作也会阻塞直到有其他Goroutine发送数据。如果只有一个Goroutine进行发送或者接收操作,而没有对应的接收或发送方,就会导致该Goroutine卡住。
ch := make(chan int) go func() { ch <- 1 // 这里会一直阻塞,因为没有接收者 }()
- 缓冲Channel满或空时的阻塞:
- 对于有缓冲Channel,当缓冲已满时,发送操作会阻塞;当缓冲为空时,接收操作会阻塞。如果在程序逻辑中没有合理处理这些情况,可能会导致Goroutine卡住。例如,不断向已满的缓冲Channel发送数据而没有及时接收。
ch := make(chan int, 2) ch <- 1 ch <- 2 go func() { ch <- 3 // 这里会阻塞,因为缓冲已满 }()
- 关闭Channel后继续发送:
- 关闭Channel后,再向其发送数据会导致运行时恐慌(panic),如果程序中没有恰当处理这种情况,可能导致整个程序异常,间接使得其他相关Goroutine卡住。而在接收端,如果没有通过
ok
标志检查Channel是否关闭就继续接收,也可能出现意外情况。
ch := make(chan int) close(ch) go func() { ch <- 1 // 这里会panic }()
- 关闭Channel后,再向其发送数据会导致运行时恐慌(panic),如果程序中没有恰当处理这种情况,可能导致整个程序异常,间接使得其他相关Goroutine卡住。而在接收端,如果没有通过
- 未关闭的Channel导致的泄漏:
- 如果一个Goroutine向一个Channel发送数据,而该Channel没有被接收端正确关闭,并且发送Goroutine一直在等待接收端接收数据,这就会导致该发送Goroutine一直阻塞,造成资源泄漏。同时,在接收端,如果没有处理好Channel的关闭情况,也可能会导致Goroutine卡在接收操作上。
ch := make(chan int) go func() { for { data, ok := <-ch if!ok { break } // 处理data } }() // 没有关闭ch,发送Goroutine可能一直阻塞
其他情况
- 系统调用阻塞:
- 如果Goroutine执行一些系统调用(如
syscall
包中的函数),这些系统调用本身可能会阻塞,例如等待I/O操作完成(如文件读取、网络请求等)。如果没有正确处理这些阻塞操作,可能会导致Goroutine长时间卡住。例如,使用syscall.Read
读取一个网络套接字,但网络连接出现问题,数据无法及时到达,Goroutine就会一直阻塞在该读取操作上。
- 如果Goroutine执行一些系统调用(如
- 无限循环无调度点:
- 如果Goroutine内部有一个无限循环且没有任何可以让Go运行时调度器进行调度的操作(如Channel操作、系统调用、调用
sync.Mutex
的方法等),那么该Goroutine会一直占用CPU资源,不会让出执行权,导致其他Goroutine无法执行,看起来就像是卡住了。
go func() { for { // 这里没有调度点,其他Goroutine无法执行 } }()
- 如果Goroutine内部有一个无限循环且没有任何可以让Go运行时调度器进行调度的操作(如Channel操作、系统调用、调用