Go通道实现模拟内核进程通信时遭遇死锁问题求助
Go Channel死锁排查与内核-进程通信架构实现
问题背景
我是Go Channel新手,正在通过模拟内核与进程的Channel通信学习相关知识。目标是让两个进程通过单个Channel向内核发送内存请求,但程序触发了死锁。
原代码
package main import ( "fmt" "log" "time" ) const ( _ float64 = iota LowPrio MedPrio HghPrio ) // Kernel type to communicate between processes and memory resources type Kernel struct { reqMemCh chan chan int } func (k *Kernel) Init() { k.reqMemCh = make(chan chan int, 2) } // Fetch memory on process request func (k *Kernel) GetReqMemCh() chan chan int { return k.reqMemCh } func (k *Kernel) AllocMem() { // loop over the items (process reply channels) received over // the request channel for pCh := range k.GetReqMemCh() { // rpCh := make(chan int) // m := <-rpCh // for now think 0 is the available index // send this as a reply to the exclusive process reply channel pCh <- 0 close(pCh) } } // Process type which requests memory type Proc struct { ind int prio float64 exeT time.Time count int memInd int rqMemCh chan chan int } func (p *Proc) Init( ind int, prio float64, rqMemCh chan chan int, ) { p.ind = ind p.prio = prio p.memInd = -1 p.rqMemCh = rqMemCh } func (p *Proc) GetReqMemCh() chan chan int { return p.rqMemCh } func (p *Proc) ReqMem() { // create the reply channel exclusive to the process // this channel will return the allocated memeory id/address rpCh := make(chan int) // send the reply channel through the request channel // to get back the allocation memory id p.GetReqMemCh() <- rpCh // Below line is blocking ... for mi := range rpCh { p.memInd = mi } } func (p Proc) String() string { return fmt.Sprintf( "Proc(%d): Memory(%d), Count(%d)", p.ind+1, p.memInd+1, p.count, ) } func main() { k := &Kernel{} k.Init() go k.AllocMem() p := &Proc{} for i := 0; i < 1; i++ { p.Init(i, LowPrio, k.GetReqMemCh()) p.ReqMem() } time.Sleep(time.Second) }
错误输出
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.(*Proc).ReqMem(...) main.go:65 main.main() main.go:86 +0x14e goroutine 6 [chan receive]: main.(*Kernel).AllocMem(0x0?) main.go:31 +0x59 created by main.main in goroutine 1 main.go:81 +0x98 exit status 2
需要实现的架构:
+--------+
| | +---------+
| P1 -\ | |
| |--\ +----------------------+ | |
+--------+ -\ | | | KERNEL |
--| CHANNEL -------| |
-/ | | | |
+---------+ -/ +----------------------+ | |
| -/ +---------+
| P2 /|
| |
+---------+
问题分析与修复
死锁原因
- 无缓冲通道的循环等待:进程创建的
rpCh是无缓冲通道,内核执行pCh <- 0时会阻塞,必须等待进程接收数据;而进程此时正卡在for mi := range rpCh等待数据,双方互相等待形成死锁。 - 同步调用阻塞主goroutine:主goroutine直接同步调用
p.ReqMem(),导致主goroutine被占用,无法触发调度,内核goroutine的处理逻辑也无法正常推进。
修复后的代码
package main import ( "fmt" "time" ) const ( _ float64 = iota LowPrio MedPrio HghPrio ) // Kernel type to communicate between processes and memory resources type Kernel struct { reqMemCh chan chan int } func (k *Kernel) Init() { k.reqMemCh = make(chan chan int, 2) } func (k *Kernel) GetReqMemCh() chan chan int { return k.reqMemCh } func (k *Kernel) AllocMem() { for pCh := range k.GetReqMemCh() { // 发送内存索引到进程的回复通道 pCh <- 0 close(pCh) } } // Process type which requests memory type Proc struct { ind int prio float64 memInd int rqMemCh chan chan int } func (p *Proc) Init(ind int, prio float64, rqMemCh chan chan int) { p.ind = ind p.prio = prio p.memInd = -1 p.rqMemCh = rqMemCh } func (p *Proc) GetReqMemCh() chan chan int { return p.rqMemCh } func (p *Proc) ReqMem() { // 创建带缓冲的回复通道,避免发送时阻塞 rpCh := make(chan int, 1) p.GetReqMemCh() <- rpCh // 接收内存索引 for mi := range rpCh { p.memInd = mi fmt.Printf("进程%d已分配内存索引%d\n", p.ind+1, mi) } } func (p Proc) String() string { return fmt.Sprintf("Proc(%d): Memory(%d)", p.ind+1, p.memInd+1) } func main() { k := &Kernel{} k.Init() go k.AllocMem() // 创建两个进程,用goroutine异步执行请求逻辑 procs := make([]*Proc, 2) for i := 0; i < 2; i++ { procs[i] = &Proc{} procs[i].Init(i, LowPrio, k.GetReqMemCh()) go procs[i].ReqMem() } // 等待所有进程完成请求 time.Sleep(time.Second * 2) for _, p := range procs { fmt.Println(p.String()) } }
架构实现说明
- 单一共享请求通道:内核创建的
reqMemCh作为所有进程与内核通信的唯一通道,符合你给出的架构图,所有进程的请求都通过这个通道传递。 - 专属回复通道:每个进程创建独立的回复通道,内核通过该通道将内存分配结果返回给对应进程,确保响应的一对一映射。
- 异步处理:进程的请求逻辑在独立goroutine中运行,主goroutine不会被阻塞,内核的内存分配逻辑也在独立goroutine中执行,实现并发处理。
- 缓冲通道解决死锁:将进程的回复通道设为带缓冲(容量1),内核发送响应时无需等待进程接收即可完成,打破循环等待的死锁条件。
内容的提问来源于stack exchange,提问作者DDStackoverflow
相关产品推荐
相关产品推荐

