为什么尽管我在发送完所有值后尝试关闭通道,仍然出现死锁?

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

why there is deadlock although i tried closing the channel after sending all values

问题

为什么在打印所有值之后会出现死锁?根据我的理解,从接收部分的代码来看,通道在等待,这导致主Go例程被阻塞或暂停,尽管我尝试使用了WaitGroup,但没有起作用。

package main

import (
	"fmt"
	//"sync"
)

//输出从10到100的值
func main() {
	//wg := sync.WaitGroup{}

	done := make(chan int)
	for i := 1; i <= 10; i++ {
		//wg.Add(1)
		go func(i int) {

			done <- i * 10
		}(i)
		// close(done)
	}
	//	close(done)
	//wg.Wait()

	// for item := range done{
	// fmt.Println(item)}
	for {
		if value, ok := <-done; ok {
			fmt.Println("received is ", value)
		} else {
			return
			//os.Exit(1)
		}
	}
}

以上是你提供的代码。

英文:

why there is deadlock after printing all values ?
what i understand
as from receiving part code channel is waiting which letting to block or pause main go routine although i tried with waitgroup doesn't work

package main

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

//output  from 10 20 30 ... - 100
func main() {
	//wg := sync.WaitGroup{}

	done := make(chan int)
	for i := 1; i &lt;= 10; i++ {
		//wg.Add(1)
		go func(i int) {

			done &lt;- i * 10
		}(i)
		// close(done)
	}
	//	close(done)
	//wg.Wait()

	// for item := range done{
	// fmt.Println(item)}
	for {
		if value, ok := &lt;-done; ok {
			fmt.Println(&quot;received is &quot;, value)
		} else {
			return
			//os.Exit(1)
		}
	}
}

答案1

得分: 0

因为你从未关闭通道,所以value, ok := <-done部分一直在等待第11个永远不会到来的值。

替换这部分代码应该可以解决问题:

for i := 1; i <= 10; i++ {
    //wg.Add(1)
    go func(i int) {
        done <- i * 10
    }(i)
    // close(done)
}

新代码

go func(){
   for i := 1; i <= 10; i++ {
        done <- i * 10 
    }
    close(done)
}()
英文:

Because you never close the channel. So the value, ok := &lt;-done part is always waiting for the 11th value that will never come.

Replacing this part should do the trick:

for i := 1; i &lt;= 10; i++ {
    //wg.Add(1)
    go func(i int) {

        done &lt;- i * 10
    }(i)
    // close(done)
}

new:

go func(){
   for i := 1; i &lt;= 10; i++ {
            done &lt;- i * 10 
    }
    close(done)
}()

答案2

得分: 0

根据@aureliar的回答,value, ok := <-done会阻塞,直到从通道接收到一个值或者通道关闭(一旦你的goroutine完成,这两种情况都不会发生)。根据你的问题和代码中的注释,看起来你已经接近解决这个问题,通过等待goroutine完成并关闭通道。

因为你事先知道goroutine的数量(每个goroutine总是在通道上发送一个值),所以有一个简单的解决方案(playground):

func main() {
	noOfGoRoutines := 10
	done := make(chan int)
	for i := 1; i <= noOfGoRoutines; i++ {
		go func(i int) {
			done <- i * 10
		}(i)
	}

	for noOfGoRoutines > 0 {
		value := <-done
		fmt.Println("received is ", value)
		noOfGoRoutines--
	}
}

当你不知道事先会接收到多少个值时,情况会变得更加复杂。在这种情况下,关闭通道是一种让接收方知道你已经完成的好方法。在你的情况下,这意味着在goroutine完成后关闭通道(这很重要,因为向关闭的通道发送会导致panic)。

要使用WaitGroup来实现这一点,你需要三个函数:

  • wg.Add(delta int)用于设置计数器。你已经注释掉的调用是可以的,但另一种方法是在进入循环之前调用wg.Add(10)
  • wg.Done()“将WaitGroup计数器减一” - 你需要在每个goroutine结束之前调用它(通常使用defer调用)。你的代码中缺少了这个。
  • wg.Wait()“阻塞直到WaitGroup计数器为零”。

有了上述代码,你可以安全地调用close(done),知道不会再向通道发送任何内容。然而,这里有一个复杂之处 - 如果你只是在第一个循环之后添加这段代码,你将遇到另一个死锁,因为你的goroutine都会在done <- i * 10处阻塞。这是因为main将在wg.Wait()处阻塞,意味着没有任何东西从通道接收,根据规范

如果容量为零或不存在,则通道是无缓冲的,只有在发送方和接收方都准备好时,通信才成功。

这可以通过在另一个goroutine中等待/关闭来解决。

你可以在playground中尝试这个代码。

package main

import (
	"fmt"
	"sync"
)

//output  from 10 20 30 ... - 100
func main() {
	wg := sync.WaitGroup{}

	done := make(chan int)
	for i := 1; i <= 10; i++ {
		wg.Add(1)
		go func(i int) {
			done <- i * 10
			wg.Done()
		}(i)
	}

	go func() {
		wg.Wait()
		close(done)
	}()

	for value := range done {
		fmt.Println("received is ", value)
	}
	fmt.Println("channel closed")

	/* I have simplified your loop but this would also work
	for {
		if value, ok := <-done; ok {
			fmt.Println("received is ", value)
		} else {
			fmt.Println("channel closed")
			return
		}
	}
	*/
}
英文:

As per the answer from @aureliar value, ok := &lt;-done will block until a value is received on the channel or the channel is closed (and, once your goroutines complete, neither of these happen). From your question and the comments in your code it looks like you were close to working out how to solve this by waiting for the goroutines to complete and closing the channel.

Because you know the number of goroutines in advance (and each goroutine always sends one value on the channel) there is a simple solution (playground):

func main() {
	noOfGoRoutines := 10
	done := make(chan int)
	for i := 1; i &lt;= noOfGoRoutines; i++ {
		go func(i int) {
			done &lt;- i * 10
		}(i)
	}

	for noOfGoRoutines &gt; 0 {
		value := &lt;-done
		fmt.Println(&quot;received is &quot;, value)
		noOfGoRoutines--
	}
}

Things get a bit more complicated when you don't know how many values will be received in advance. In that case closing the channel is a good way of letting the receiver know you have finished. In your case this means closing the channel after the goroutines have completed (this is important because sending to a closed channel leads to a panic).

To do this using a WaitGroup you will need three functions:

  • wg.Add(delta int) to set the counter. The call you have commented out is fine but an alternative is to call wg.Add(10) before entering the loop.
  • wg.Done() "decrements the WaitGroup counter by one" - you need to call this before each goroutine ends (its common to defer the call). This was missing from your code.
  • wg.Wait() "blocks until the WaitGroup counter is zero".

With the above in place you can safely call close(done) knowing that nothing further will be sent to the channel. However there is a complication - if you just add this code after your first loop you will hit another deadlock because your goroutines will all block at done &lt;- i * 10. This happens because main will be blocked at wg.Wait() meaning nothing is receiving from the channel and as per the spec:

>If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready.

This can be solved by waiting/closing within another goroutine.

You can try this in the playground.

package main

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

//output  from 10 20 30 ... - 100
func main() {
	wg := sync.WaitGroup{}

	done := make(chan int)
	for i := 1; i &lt;= 10; i++ {
		wg.Add(1)
		go func(i int) {
			done &lt;- i * 10
			wg.Done()
		}(i)
	}

	go func() {
		wg.Wait()
		close(done)
	}()

	for value := range done {
		fmt.Println(&quot;received is &quot;, value)
	}
	fmt.Println(&quot;channel closed&quot;)

	/* I have simplified your loop but this would also work
	for {
		if value, ok := &lt;-done; ok {
			fmt.Println(&quot;received is &quot;, value)
		} else {
			fmt.Println(&quot;channel closed&quot;)
			return
		}
	}
	*/
}

huangapple
  • 本文由 发表于 2021年9月14日 16:18:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/69174292.html
匿名

发表评论

匿名网友

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

确定