在Go语言并发编程中,死锁就像一个两个人都想先过的狭窄门洞 - 你不让我,我不让你,最后谁都过不去。今天,就让我们一起揭开这个让无数Go开发者头疼的难题。
死锁到底是什么?一个生活化的比喻
想象你和朋友去餐厅吃饭,需要"筷子+勺子"才能用餐。你手里握着唯一的筷子(持有资源),等着朋友的勺子(等待资源);朋友手里握着唯一的勺子(持有资源),等着你的筷子(等待资源)。两人都不释放自己的资源,最终谁也吃不上饭——这就是死锁。
对应到Go程序中:
- "你和朋友" = 多个goroutine
- "筷子/勺子" = 程序资源(如锁、通道、文件句柄)
- "互相等待" = goroutine间的资源依赖循环
死锁的核心特征很明显:程序卡住无任何推进,日志无输出(或卡在某个步骤),CPU和内存占用极低(goroutine都在等待,无实际工作)。
死锁的四个必要条件:少一个都不会发生
死锁并非随机出现,它必须同时满足4个经典条件。只要破坏其中任意一个条件,死锁就不会发生。
条件一:互斥条件 —— 资源只能"独占使用"
某个资源同一时间只能被一个goroutine占用,其他goroutine要使用,必须等待当前占用者释放。
就像厕所隔间一次只能容纳一人,Go中的sync.Mutex(互斥锁)是典型的"互斥资源"——一个goroutine加锁后,其他goroutine必须等待其解锁才能再次加锁。
条件二:持有并等待条件 —— 拿着资源等其他资源
一个goroutine已持有至少一个资源,仍在等待其他goroutine持有的资源,且不释放自己已持有的资源。
回到餐厅例子:你握着筷子(已持有资源),还在等朋友的勺子(等待资源),且不把筷子交给朋友——这就是"持有并等待"。
条件三:不可剥夺条件 —— 资源不能"强行抢走"
一个goroutine持有的资源,不能被其他goroutine强行剥夺,只能由持有方主动释放。
比如你握着筷子,别人不能直接从你手中抢走,只能等你主动放下;Go中的互斥锁也遵循此规则。
条件四:循环等待条件 —— 互相等待对方的资源
多个goroutine形成闭环,每个goroutine都在等待下一个goroutine持有的资源。
例如:goroutineA持有资源1、等资源2;goroutineB持有资源2、等资源1——两者形成循环,永远无法获取所需资源。
Go死锁经典场景:附完整代码示例
了解了死锁的原理,让我们看几个Go中最常见的死锁场景,以及它们的完整代码示例。
场景一:无缓冲通道的同步死锁
package main
import "fmt"
func main() {

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



