为什么以下代码会导致死锁?

huangapple go评论81阅读模式
英文:

Why following code generates deadlock

问题

Golang新手在这里。有人能解释一下为什么以下代码会产生死锁吗?

我知道将true发送到boolean <- done通道,但我不想使用它。

package main

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

var wg2 sync.WaitGroup

func producer2(c chan<- int) {
	for i := 0; i < 5; i++ {
		time.Sleep(time.Second * 10)
		fmt.Println("Producer Writing to chan %d", i)
		c <- i
	}
}

func consumer2(c <-chan int) {
	defer wg2.Done()
	fmt.Println("Consumer Got value %d", <-c)

}

func main() {
	c := make(chan int)
	wg2.Add(5)
	fmt.Println("Starting .... 1")
	go producer2(c)
	go consumer2(c)
	fmt.Println("Starting .... 2")

	wg2.Wait()
}

以下是我的理解,我知道是错误的:

  1. 在生产者函数的循环中,一旦将0写入通道,通道将被阻塞。
  2. 所以我期望通道在之后被消费者清空。
  3. 当通道在步骤2中被清空时,生产者函数可以再次放入另一个值,然后被阻塞,步骤2重复执行。
英文:

Golang newbie here. Can somebody explain why the following code generates a deadlock?

I am aware of sending true to boolean <- done channel but I don't want to use it.

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var wg2 sync.WaitGroup

func producer2(c chan&lt;- int) {
	for i := 0; i &lt; 5; i++ {
		time.Sleep(time.Second * 10)
		fmt.Println(&quot;Producer Writing to chan %d&quot;, i)
		c &lt;- i
	}
}

func consumer2(c &lt;-chan int) {
	defer wg2.Done()
	fmt.Println(&quot;Consumer Got value %d&quot;, &lt;-c)

}

func main() {
	c := make(chan int)
	wg2.Add(5)
	fmt.Println(&quot;Starting .... 1&quot;)
	go producer2(c)
	go consumer2(c)
	fmt.Println(&quot;Starting .... 2&quot;)

	wg2.Wait()
}

Following is my understanding and I know that it is wrong:

  1. The channel will be blocked the moment 0 is written to it within the
    loop of producer function
  2. So I expect channel to be emptied by the
    consumer afterwards.
  3. As the channel is emptied in the step 2,
    producer function can again put in another value and then get
    blocked and steps 2 repeats again.

答案1

得分: 3

你原始的死锁问题是由wg2.Add(5)引起的,你等待5个goroutine完成,但只有一个完成了;你只调用了一次wg2.Done()。将其更改为wg2.Add(1),你的程序将可以正常运行。

然而,我怀疑你的意图是消费通道中的所有值,而不仅仅是一个。如果你将消费者函数改为:

func consumer2(c <-chan int) {
    defer wg2.Done()
    for i := range c {
        fmt.Printf("Consumer Got value %d\n", i)
    }
}

你将遇到另一个死锁问题,因为在生产者函数中没有关闭通道,消费者在等待更多的值,但这些值永远不会到达。在生产者函数中添加close(c)将解决这个问题。

英文:

Your original deadlock is caused by wg2.Add(5), you were waiting for 5 goroutines to finish, but only one did; you called wg2.Done() once. Change this to wg2.Add(1), and your program will run without error.

However, I suspect that you intended to consume all the values in the channel not just one as you do. If you change consumer function to:

func consumer2(c &lt;-chan int) {
    defer wg2.Done()
    for i := range c {
        fmt.Printf(&quot;Consumer Got value %d\n&quot;, i)
    }
}

You will get another deadlock because the channel is not closed in producer function, and consumer is waiting for more values that never arrive. Adding close(c) to the producer function will fix it.

答案2

得分: 1

为什么会出现错误?

运行你的代码会出现以下错误:

➜  gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
fatal error: all goroutines are asleep - deadlock!

以下是错误原因:

你的代码中有三个 goroutine:mainproducer2consumer2。当代码运行时,

  • producer2 向通道发送数字 0
  • consumer2 从通道接收到 0,然后退出
  • producer2 向通道发送 1,但是没有消费者,因为 consumer2 已经退出了
  • producer2 正在等待
  • main 执行 wg2.Wait(),但是并没有关闭所有的 waitgroup。所以main 正在等待

这里有两个概念你可能混淆了:

  1. waitgroup 的工作原理
  2. 如何从通道接收所有的值

我会简要解释一下它们,因为互联网上已经有很多相关文章了。

waitgroup 的工作原理

WaitGroup 是一种等待所有 goroutine 完成的方式。在后台运行 goroutine 时,知道它们何时退出非常重要,这样才能执行某些操作。

在你的情况下,我们运行了两个 goroutine,所以在开始时应该设置 wg2.Add(2),每个 goroutine 都应该在完成时调用 wg2.Done() 来通知它已经完成。

从通道接收数据

当从通道接收数据时,如果你知道它将发送多少个数据,可以使用以下方式的 for 循环:

for i:=0; i<N; i++ {
    data := <-c
    process(data)
}

否则,可以使用以下方式:

for data := range c {
    process(data)
}

此外,当没有更多数据要发送时,不要忘记关闭通道

如何修复它?

根据上述解释,代码可以修复如下:

package main

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

var wg2 sync.WaitGroup

func producer2(c chan<- int) {
	defer wg2.Done()
	for i := 0; i < 5; i++ {
		time.Sleep(time.Second * 1)
		fmt.Printf("Producer Writing to chan %d\n", i)
		c <- i
	}
	close(c)
}

func consumer2(c <-chan int) {
	defer wg2.Done()
	for i := range c {
		fmt.Printf("Consumer Got value %d\n", i)
	}

}

func main() {
	c := make(chan int)
	wg2.Add(2)
	fmt.Println("Starting .... 1")
	go producer2(c)
	go consumer2(c)
	fmt.Println("Starting .... 2")

	wg2.Wait()
}

这里 是另一种可能的修复方式。

预期输出

修复后的代码会产生以下输出:

➜  gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
Consumer Got value 1
Producer Writing to chan 2
Consumer Got value 2
Producer Writing to chan 3
Consumer Got value 3
Producer Writing to chan 4
Consumer Got value 4
英文:

Why it error?

Running your code gets the following error:

➜  gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
fatal error: all goroutines are asleep - deadlock!

Here is why:

There are three goroutines in your code: main,producer2 and consumer2. When it runs,

  • producer2 sends a number 0 to the channel
  • consumer2 recives 0 from the channel, and exits
  • producer2 sends 1 to the channel, but no one is consuming, since consumer2 already exits
  • producer2 is waiting
  • main executes wg2.Wait(), but not all waitgroup are closed. So main is waiting

Two goroutines are waiting here, does nothing, and nothing will be done no matter how long you wait. It is a deadlock! Golang detects it and panic.

There are two concepts you are confused here:

  1. how waitgourp works
  2. how to receive all values from a channel

I'll explain them here briefly, there are alreay many articles out there on the internet.

how waitgroup works

WaitGroup if a way to wait for all groutine to finish. When running goroutines in the background, it's important to know when all of them quits, then certain action can be conducted.

In your case, we run two goroutines, so at the beginning we should set wg2.Add(2), and each goroutine should add wg2.Done() to notify it is done.

Receive data from a channel

When receiving data from a channel. If you know exactly how many data it will send, use for loop this way:

for i:=0; i&lt;N; i++ {
    data = &lt;-c
    process(data)
}

Otherwise use it this way:

for data := range c {
    process(data)
}

Also, Don't forget to close channel when there is no more data to send.

How to fix it?

With the above explanation, the code can be fixed as:

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var wg2 sync.WaitGroup

func producer2(c chan&lt;- int) {
	defer wg2.Done()
	for i := 0; i &lt; 5; i++ {
		time.Sleep(time.Second * 1)
		fmt.Printf(&quot;Producer Writing to chan %d\n&quot;, i)
		c &lt;- i
	}
	close(c)
}

func consumer2(c &lt;-chan int) {
	defer wg2.Done()
	for i := range c {
		fmt.Printf(&quot;Consumer Got value %d\n&quot;, i)
	}

}

func main() {
	c := make(chan int)
	wg2.Add(2)
	fmt.Println(&quot;Starting .... 1&quot;)
	go producer2(c)
	go consumer2(c)
	fmt.Println(&quot;Starting .... 2&quot;)

	wg2.Wait()
}

Here is another possible way to fix it.

The expected output

Fixed code gives the following output:

➜  gochannel go run dl.go
Starting .... 1
Starting .... 2
Producer Writing to chan 0
Consumer Got value 0
Producer Writing to chan 1
Consumer Got value 1
Producer Writing to chan 2
Consumer Got value 2
Producer Writing to chan 3
Consumer Got value 3
Producer Writing to chan 4
Consumer Got value 4

huangapple
  • 本文由 发表于 2017年2月20日 12:16:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/42336399.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定