Golang中遇到致命错误:所有goroutine都处于休眠状态——死锁问题!

Golang中遇到致命错误:所有goroutine都处于休眠状态——死锁问题! 我正在尝试让两个 goroutine 对一个整数进行递增。

package main

import (
	"fmt"
	"sync"
)

var sum = 0
var wg sync.WaitGroup

func main() {
	wg.Add(2)
	ch := make(chan int)
	ch <- sum

	go func() {
		sum = <-ch
		sum++
		ch <- sum
		defer wg.Done()
	}()

	go func() {
		sum = <-ch
		sum++
		ch <- sum
		defer wg.Done()
	}()

	wg.Wait()
	i := <-ch
	close(ch)
	fmt.Println(i)
}

出现的错误是:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /home/peter/work/src/misc2/go-routines-addition/main.go:14 +0x72
exit status 2
>>
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /home/peter/work/src/misc2/go-routines-addition/main.go:14 +0x72
exit status 2

代码可以在这里找到:

https://play.golang.org/p/wyKGpqFWtmG

请问我哪里做错了?


更多关于Golang中遇到致命错误:所有goroutine都处于休眠状态——死锁问题!的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

太棒了,谢谢!

更多关于Golang中遇到致命错误:所有goroutine都处于休眠状态——死锁问题!的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这只是一次为了更好地学习通道而进行的学术性尝试。 感谢您的帮助!

如果你正在学习Go语言,可以跟随这些教程,它们非常棒 https://quii.gitbook.io/learn-go-with-tests/

在尝试了您的解决方案后,我注意到您只调用了 wg.Add(1) 一次。 这里有两个 goroutine,我的理解是应该为每个 goroutine 调用一次 Add。 您能解释一下吗?

是的,你说得对,使用“2”而不是“1”是有道理的,因为有两个 goroutine。如果你使用“2”,就会出现数据竞争,因为代码运行时一个 goroutine 已经结束了。数据竞争条件发生在 goroutine (1) 执行 ch <- sum 之后,控制权交回主线程,此时 wg.Wait() 被执行,但没有 goroutine 在运行。 让我们继续探讨如何解决…

我不太理解在你的代码中通道(channels)的用途。 这里是一种实现方式:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

var sum int64

func main() {
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		atomic.AddInt64(&sum, 1)
	}()

	go func() {
		defer wg.Done()
		atomic.AddInt64(&sum, 1)
	}()

	wg.Wait()
	fmt.Println(sum)
}

在你执行 ch <- sum 这一行时,主线程会被阻塞,但此时没有其他可用的 goroutine,因为你是在这行之后才定义并运行它们的。将这行代码移到 wg.Wait() 之前,可能就能解决问题……

我通常会添加一些 println 来追踪程序的执行情况。

package main

import (
"fmt"
"sync"
)

var sum = 0
var wg sync.WaitGroup

func main() {
wg.Add(1)
ch := make(chan int)

go func() {
	defer func() {
		fmt.Println("(1) defering...")
		wg.Done()
	}()

	fmt.Println("(1) getting channel...")
	sum = <-ch
	fmt.Println("(1) sum=", sum)

	sum++
	fmt.Println("(1) Increment=", sum)
	ch <- sum
	fmt.Println("(1) put in channel...")
}()

go func() {
	defer func() {
		fmt.Println("(2) defering...")
		wg.Done()
	}()

	fmt.Println("(2) getting channel...")
	sum = <-ch
	fmt.Println("(2) sum=", sum)

	sum++
	fmt.Println("(2) Increment=", sum)
	ch <- sum
	fmt.Println("(2) put in channel...")
}()

fmt.Println("(Main) Sending sum to channel:", sum)
ch <- sum

fmt.Println("(Main) Waiting...")
wg.Wait()

fmt.Println("(Main) Reading from channel...")
i := <-ch
fmt.Println("(Main) Closing channel...")

close(ch)
fmt.Println("(Main) Reading last value...")
fmt.Println(i)
}

你的代码存在两个关键问题导致了死锁:

  1. 无缓冲通道的同步问题:在第14行 ch <- sum 会阻塞主goroutine,直到有另一个goroutine从通道接收数据
  2. goroutine执行顺序问题:两个goroutine都试图从通道接收数据,但主goroutine已经阻塞在发送操作上

这是修复后的代码:

package main

import (
	"fmt"
	"sync"
)

var sum = 0
var wg sync.WaitGroup

func main() {
	wg.Add(2)
	ch := make(chan int)
	
	// 启动goroutine后再发送初始值
	go func() {
		val := <-ch
		val++
		ch <- val
		wg.Done()
	}()

	go func() {
		val := <-ch
		val++
		ch <- val
		wg.Done()
	}()

	// 现在发送初始值到通道
	ch <- sum
	
	wg.Wait()
	i := <-ch
	close(ch)
	fmt.Println(i)  // 输出: 2
}

或者使用带缓冲的通道更简单:

package main

import (
	"fmt"
	"sync"
)

var sum = 0
var wg sync.WaitGroup

func main() {
	wg.Add(2)
	// 使用带缓冲的通道
	ch := make(chan int, 1)
	ch <- sum

	go func() {
		val := <-ch
		val++
		ch <- val
		wg.Done()
	}()

	go func() {
		val := <-ch
		val++
		ch <- val
		wg.Done()
	}()

	wg.Wait()
	i := <-ch
	close(ch)
	fmt.Println(i)  // 输出: 2
}

关键修改:

  1. 先启动goroutine再发送数据到无缓冲通道,或者使用带缓冲的通道
  2. 移除了defer wg.Done(),直接在操作完成后调用wg.Done()
  3. 使用局部变量val避免直接操作全局变量sum,减少竞态条件风险

注意:这个示例仍然存在竞态条件,两个goroutine可能以不确定的顺序执行。如果需要确定性的结果,应该使用互斥锁或其他同步机制。

回到顶部