Golang中总是遇到死锁问题!为什么?

Golang中总是遇到死锁问题!为什么?

package main

import "fmt"

func main() {
    c := make(chan int)

    for i := 0; i < 10; i++ {
        go func() {
            for j := 0; j < 10; j++ {
                c <- j
            }
        }()
    }
    // 在使用 range 子句时,最后总是会发生死锁。
    // close(c) 也不起作用
    for range c {
        fmt.Println(<-c)
    }
    // 但这个可以正常工作。为什么??
    for k := 0; k < 100; k++ {
        fmt.Println(<-c)
    }
}

更多关于Golang中总是遇到死锁问题!为什么?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

你从未关闭通道。但你需要从正确的位置关闭它。

之前你在哪里尝试过?

更多关于Golang中总是遇到死锁问题!为什么?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


那么,你的意思是在这种情况下我们不能使用 range 子句吗?

两者:

  • for i 循环终止之前
  • go func() 终止之前

for k := 0; k < 100; k++ 循环运行 100 次,并从通道中读取 100 个值——这是可以的。

for range c 循环会无限运行,并消耗通道中的所有值,直到所有 goroutine 都完成。它会继续从空通道中读取,因为剩下的唯一例程会引发死锁。

func main() {
    fmt.Println("hello world")
}

当然可以,你只需要一个 sync.WaitGroup 和另一个协程。

点击查看剧透

https://play.golang.org/p/6x77FRv9C-R

死锁的原因是for range cfmt.Println(<-c)的组合导致了双重接收。for range c会不断从通道接收值,但你在循环体内又执行了一次<-c,这会导致额外的接收操作阻塞。

示例代码分析:

// 问题代码
for range c {          // 第一次接收(隐式)
    fmt.Println(<-c)   // 第二次接收(显式)
}

实际执行流程:

  1. for range c执行隐式接收value := <-c
  2. fmt.Println(<-c)执行第二次接收
  3. 发送方只发送了100个值,但这里需要200次接收(100×2)
  4. 最后10个goroutine在发送第101-200个值时全部阻塞,主goroutine在等待第201次接收时阻塞

解决方案:

// 正确写法1:直接使用range接收的值
for v := range c {
    fmt.Println(v)
}

// 正确写法2:显式接收
for {
    v, ok := <-c
    if !ok {
        break
    }
    fmt.Println(v)
}

// 完整修正示例
package main

import (
    "fmt"
    "sync"
)

func main() {
    c := make(chan int)
    var wg sync.WaitGroup
    
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := 0; j < 10; j++ {
                c <- j
            }
        }()
    }
    
    // 关闭通道的goroutine
    go func() {
        wg.Wait()
        close(c)
    }()
    
    // 正确接收
    for v := range c {
        fmt.Println(v)
    }
}

第二个for循环能正常工作的原因:

for k := 0; k < 100; k++ {  // 明确知道接收100次
    fmt.Println(<-c)        // 每次循环只接收一次
}

这里没有使用range,明确指定了接收100次,每次循环只执行一次<-c,发送和接收次数匹配(100次发送,100次接收),所以不会死锁。

关键点:

  1. for range channel会一直尝试接收直到通道关闭
  2. 确保发送和接收次数匹配
  3. 使用WaitGroup协调goroutine,在发送完成后关闭通道
回到顶部