You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 /|
| |
+---------+


问题分析与修复

死锁原因

  1. 无缓冲通道的循环等待:进程创建的rpCh是无缓冲通道,内核执行pCh <- 0时会阻塞,必须等待进程接收数据;而进程此时正卡在for mi := range rpCh等待数据,双方互相等待形成死锁。
  2. 同步调用阻塞主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())
    }
}

架构实现说明

  1. 单一共享请求通道:内核创建的reqMemCh作为所有进程与内核通信的唯一通道,符合你给出的架构图,所有进程的请求都通过这个通道传递。
  2. 专属回复通道:每个进程创建独立的回复通道,内核通过该通道将内存分配结果返回给对应进程,确保响应的一对一映射。
  3. 异步处理:进程的请求逻辑在独立goroutine中运行,主goroutine不会被阻塞,内核的内存分配逻辑也在独立goroutine中执行,实现并发处理。
  4. 缓冲通道解决死锁:将进程的回复通道设为带缓冲(容量1),内核发送响应时无需等待进程接收即可完成,打破循环等待的死锁条件。

内容的提问来源于stack exchange,提问作者DDStackoverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:15:55