Golang中为什么会出现死锁问题

Golang中为什么会出现死锁问题 为什么在这种情况下第23行(<- done)会出现死锁

package  main


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

go func() {
	for i := 0; i < 50; i++ {
		c <- i
	}
	done <- true
}()

go func() {
	for i := 0; i < 50; i++ {
		c <- i
	}
	done <- true
}()


<- done
<- done

close(c)

for i := range c {
	println(i)
 }
}

但在这种情况下没有死锁

package  main

import "time"

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



go func() {
	for i := 0; i < 50; i++ {
		<- c
	}
}()


time.Sleep(time.Second * 5)
}

另外为什么当主线程睡眠时不会出现死锁?


更多关于Golang中为什么会出现死锁问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

但在第二个示例中,虽然只进行写入操作而没有从通道读取,但它仍然正常工作

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


因为你没有在那里等待一个 done 通道。你的主 Go 协程在 5 秒后直接退出,并不关心其子协程的状态。

c 是无缓冲的,在第一个项目被读取之前不会接受第二个项目。由于你从未从 c 中读取数据,任何循环都无法成功完成,因此永远不会向 done 发送任何内容。

这是一个典型的Go语言并发编程中的死锁问题。让我详细解释死锁产生的原因和解决方案。

死锁原因分析

在第一个代码中,死锁发生在第23行 <- done,主要原因如下:

  1. 无缓冲通道的阻塞特性cdone 都是无缓冲通道,发送和接收操作必须同步进行
  2. 发送操作被阻塞:两个goroutine都在向通道c发送数据,但主goroutine在接收done信号之前没有从c接收数据
  3. 执行顺序问题:主goroutine先等待done信号,但发送done信号的goroutine被阻塞在向c发送数据

具体执行流程:

// 问题代码的执行顺序
go func() {           // goroutine1启动
    for i := 0; i < 50; i++ {
        c <- i        // 阻塞!没有接收者
    }
    done <- true      // 永远无法执行到这里
}()

<- done  // 主goroutine阻塞等待,死锁!

修复方案

方案1:使用带缓冲的通道

package main

func main() {
    c := make(chan int, 100)  // 添加缓冲区
    done := make(chan bool)

    go func() {
        for i := 0; i < 50; i++ {
            c <- i
        }
        done <- true
    }()

    go func() {
        for i := 0; i < 50; i++ {
            c <- i
        }
        done <- true
    }()

    <-done
    <-done
    close(c)

    for i := range c {
        println(i)
    }
}

方案2:分离发送和接收

package main

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

    // 启动发送goroutine
    go func() {
        for i := 0; i < 50; i++ {
            c <- i
        }
        done <- true
    }()

    go func() {
        for i := 0; i < 50; i++ {
            c <- i
        }
        done <- true
    }()

    // 启动接收goroutine
    go func() {
        <-done
        <-done
        close(c)
    }()

    for i := range c {
        println(i)
    }
}

方案3:使用select避免阻塞

package main

func main() {
    c := make(chan int)
    done := make(chan bool, 2)  // 缓冲done通道

    go func() {
        for i := 0; i < 50; i++ {
            select {
            case c <- i:
            default:
                // 处理发送失败的情况
            }
        }
        done <- true
    }()

    go func() {
        for i := 0; i < 50; i++ {
            select {
            case c <- i:
            default:
                // 处理发送失败的情况
            }
        }
        done <- true
    }()

    <-done
    <-done
    close(c)

    for i := range c {
        println(i)
    }
}

关于第二个代码的问题

第二个代码没有死锁的原因是:

package main

import "time"

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

    go func() {
        for i := 0; i < 50; i++ {
            <- c  // 从空通道接收会阻塞,但goroutine不会退出
        }
    }()

    time.Sleep(time.Second * 5)  // 主goroutine睡眠,不执行其他操作
    // 程序正常结束,所有goroutine被终止
}

这里没有死锁是因为:

  1. 接收goroutine在空通道上阻塞,但不会导致程序死锁
  2. 主goroutine睡眠5秒后程序直接退出
  3. Go运行时在程序退出时会终止所有goroutine

死锁检测只发生在所有goroutine都处于阻塞状态时。在这个例子中,主goroutine在睡眠期间不是阻塞状态(睡眠是可唤醒的等待),因此不会被判定为死锁。

回到顶部