go语言为什么channel的close和接收需要放在不同的协程,且close不能放在主goroutine中,否则会报错all goroutines are asleep - deadlock?

以下的代码会报错:

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, sem chan struct{}, wg *sync.WaitGroup) int {
    defer wg.Done()

    // 请求一个资源

    // 模拟耗时操作
    fmt.Printf("Worker %d started\n", id)
    time.Sleep(2 * time.Second)
    fmt.Printf("Worker %d finished\n", id)

    // 释放一个资源
    // <-sem
    return 1
}

func main() {
    // 定义最大并发数
    const maxConcurrency = 3

    // 创建带缓冲的通道,用于限制并发数
    sem := make(chan struct{}, maxConcurrency)
    result := make(chan int)

    // 使用 sync.WaitGroup 等待所有 goroutine 完成
    var wg sync.WaitGroup

    // 启动 10 个 worker
    for i := 1; i <= 10; i++ {
        wg.Add(1)
        go func(i int) {
            sem <- struct{}{}
            res := worker(i, sem, &wg)
            result <- res
            <-sem
        }(i)
    }

    // 等待所有 worker 完成
    // go func() {

    // }()
    wg.Wait()
    close(result)

    for i := range result {
        fmt.Printf("%v", i)
    }
}

wg.Wait()和close(result)放在主goroutine中会报错all goroutines are asleep - deadlock!。正常我的理解不是等待wg.Done,所有的协程操作完成后,这边就会wait完成,随后close channel,最后接收channel,但是会报错死锁。可是放在另外一个goroutine不也是一样的流程吗,也是wait,close,随后接收channel,有什么区别吗

阅读 1.2k
1 个回答

1.goroutine和worker goroutines之间存在一个死锁,你可以参考一下面的修改:

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, sem chan struct{}, wg *sync.WaitGroup) int {
    defer wg.Done()

    sem <- struct{}{} // 请求一个资源

    fmt.Printf("Worker %d started\n", id)
    time.Sleep(2 * time.Second)
    fmt.Printf("Worker %d finished\n", id)

    <-sem // 释放资源
    return id
}

func main() {
    const maxConcurrency = 3

    sem := make(chan struct{}, maxConcurrency)
    result := make(chan int)

    var wg sync.WaitGroup

    for i := 1; i <= 10; i++ {
        wg.Add(1)
        go func(i int) {
            res := worker(i, sem, &wg)
            result <- res
        }(i)
    }

    // 用另一个 goroutine 来接收结果
    go func() {
        wg.Wait()
        close(result)
    }()

    // 从 result channel 中获取数据
    for res := range result {
        fmt.Printf("Result: %d\n", res)
    }
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题