Go并发:所有的goroutine都处于休眠状态-死锁

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

go concurrency all goroutines are asleep - deadlock

问题

抱歉,我只能为您提供翻译服务,无法执行代码。以下是您提供的代码的翻译:

对不起,这可能是一个初学者的问题,但我对Go语言中的并发部分感到困惑。下面的程序是我正在编写的一个较大程序的简化版本,因此我希望保持结构与下面的类似。

基本上,我想使用无缓冲通道并发地运行addCount(..),而不是等待4秒钟,当int_slice中的所有元素都被处理完毕后,我想对它们进行另一种操作。然而,这个程序以一个"panic: close of closed channel"的错误结束,如果我移除通道的关闭操作,我会得到我期望的输出,但它会出现"fatal error: all goroutines are asleep - deadlock"的错误。

在这种情况下,我该如何正确实现并发部分?

提前感谢您的帮助!

package main

import (
	"fmt"
	"time"
)

func addCount(num int, counter chan<- int) {
	time.Sleep(time.Second * 2)
	counter <- num * 2
}

func main() {
	counter := make(chan int)
	int_slice := []int{2, 4}

	for _, item := range int_slice {
		go addCount(item, counter)
		close(counter)
	}

	for item := range counter {
		fmt.Println(item)
	}
}
英文:

Sorry about the noob question but I'm having a hard time wrapping my head around the concurrency part of go. Basically this program below is a simplified version of a larger one I'm writing, thus I want to keep the structure similar to below.

Basically instead of waiting 4 seconds I want to run addCount(..) concurrent using the unbuffered channel and when all elements in the int_slice has been processed I want to do another operation on them. However this program ends with a "panic: close of closed channel" and if I remove the closing of the channel I'm getting the output I'm expecting but it panics with: "fatal error: all goroutines are asleep - deadlock"

How can I implement the concurrency part correctly in this scenario?

Thanks in advance!

package main

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

func addCount(num int, counter chan&lt;- int) {
    time.Sleep(time.Second * 2)
    counter &lt;- num * 2
}

func main() {
    counter := make(chan int)
    int_slice := []int{2, 4}

    for _, item := range int_slice {
  	    go addCount(item, counter)
        close(counter)
    }

    for item := range counter {
	    fmt.Println(item)
    }
}

答案1

得分: 5

以下是我在代码中发现的问题,并基于你的实现给出的一个可工作版本。

  • 如果一个goroutine尝试向一个"无缓冲"通道写入数据,它将被阻塞,直到有其他goroutine从该通道读取数据。由于你在它们完成向通道写入数据之前没有进行读取,所以在这里出现了死锁。

  • 在它们被阻塞时关闭通道会打破死锁,但会导致错误,因为它们现在无法向已关闭的通道写入数据。

解决方案包括:

  • 创建一个带缓冲的通道,以便它们可以无阻塞地写入数据。

  • 使用sync.WaitGroup,以便在关闭通道之前等待所有goroutine完成。

  • 在所有操作完成后,从通道中读取数据。

请参考以下代码(带有注释):

package main

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

func addCount(num int, counter chan<- int, wg *sync.WaitGroup) {
    // 从同步组中移除一个
    defer wg.Done()
    time.Sleep(time.Second * 2)
    counter <- num * 2
}

func main() {
    int_slice := []int{2, 4}
    // 使用切片的大小创建带缓冲的通道,以便它们可以无阻塞地写入数据
    counter := make(chan int, len(int_slice))

    var wg sync.WaitGroup

    for _, item := range int_slice {
        // 向同步组中添加一个,表示我们需要等待一个更多的goroutine
        wg.Add(1)
        go addCount(item, counter, &wg)
    }
    
    // 等待所有goroutine结束
    wg.Wait()
    
    // 关闭通道,不再期望有数据写入
    close(counter)

    // 读取通道中剩余的值
    for item := range counter {
        fmt.Println(item)
    }

}

希望对你有帮助!

英文:

Here are the issues I spotted in the code, and below a working version based on your implementation.

  • If a goroutine tries to write to an "unbuffered" channel, it will block until someone reads from it. Since you are not reading until they finish writing to the channel, you have a deadlock there.

  • Closing the channel while they are blocked breaks the deadlock, but gives an error since they now can't write to a closed channel.

Solution involves:

  • Creating a buffered channel so that they can write without blocking.

  • Using a sync.WaitGroup so that you wait for the goroutines to finish before closing the channel.

  • Reading from the channel at the end, when all is done.

See here, with comments:

	package main
	
	import (
	    &quot;fmt&quot;
	    &quot;time&quot;
		&quot;sync&quot;
	)
	
	func addCount(num int, counter chan&lt;- int, wg *sync.WaitGroup) {
        // clear one from the sync group
		defer wg.Done()
	    time.Sleep(time.Second * 2)
	    counter &lt;- num * 2
	}
	
	func main() {
	    int_slice := []int{2, 4}
        // make the slice buffered using the slice size, so that they can write without blocking
		counter := make(chan int, len(int_slice))
	
		var wg sync.WaitGroup
	
	    for _, item := range int_slice {
            // add one to the sync group, to mark we should wait for one more
	    	wg.Add(1)
	        go addCount(item, counter, &amp;wg)
	    }
	    
        // wait for all goroutines to end
		wg.Wait()
		
        // close the channel so that we not longer expect writes to it
		close(counter)
	
        // read remaining values in the channel
	    for item := range counter {
	        fmt.Println(item)
	    }
	
	}

答案2

得分: 0

为了举例,这里是 @eugenioy 提交的稍作修改的版本。它允许使用无缓冲通道,并在值到达时立即读取,而不是像常规的 for 循环那样在最后读取。

package main

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

func addCount(num int, counter chan<- int, wg *sync.WaitGroup) {
	// 从同步组中清除一个
	defer wg.Done()
	// 不需要,除非你想要减慢输出速度
	time.Sleep(time.Second * 2)
	counter <- num * 2
}

func main() {
	// Go 中变量名不使用下划线
	intSlice := []int{2, 4}
	
	counter := make(chan int)

	var wg sync.WaitGroup

	for _, item := range intSlice {
		// 将同步组计数加一,表示我们需要等待一个更多的 goroutine
		wg.Add(1)
		go addCount(item, counter, &wg)
	}

	// 通过将等待和关闭操作放在一个 goroutine 中,我可以在它完成之前开始读取通道,而且我也不需要知道切片的大小
	go func() {
		wg.Wait()
		close(counter)
	}()

	for item := range counter {
		fmt.Println(item)
	}
}
英文:

For the sake of having examples here's a slightly modified version of what @eugenioy submitted. It allows for the use of an unbuffered channel and the reading of values as they come in instead of at the end like a regular for loop.

package main

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

func addCount(num int, counter chan&lt;- int, wg *sync.WaitGroup) {
	// clear one from the sync group
	defer wg.Done()
    // not needed, unless you wanted to slow down the output
	time.Sleep(time.Second * 2)
	counter &lt;- num * 2
}

func main() {
	// variable names don&#39;t have underscores in Go
	intSlice := []int{2, 4}
	
	counter := make(chan int)

	var wg sync.WaitGroup

	for _, item := range intSlice {
		// add one to the sync group, to mark we should wait for one more
		wg.Add(1)
		go addCount(item, counter, &amp;wg)
	}

	// by wrapping wait and close in a go routine I can start reading the channel before its done, I also don&#39;t need to know the size of the
	// slice
	go func() {
		wg.Wait()
		close(counter)
	}()

	for item := range counter {
		fmt.Println(item)
	}
}

答案3

得分: -2

package main

import (
	"fmt"
	"time"
)

func addCount(num int, counter chan<- int) {
	time.Sleep(time.Second * 2)
	counter <- num * 2
}

func main() {
	counter := make(chan int)
	int_slice := []int{2, 4}

	for _, item := range int_slice {
		go addCount(item, counter)

		fmt.Println(<-counter)
	}
}
package main

import (
	"fmt"
	"time"
)

func addCount(num int, counter chan<- int) {
	time.Sleep(time.Second * 2)
	counter <- num * 2
}

func main() {
	counter := make(chan int)
	int_slice := []int{2, 4}

	for _, item := range int_slice {
		go addCount(item, counter)

		fmt.Println(<-counter)
	}
}
英文:
package main

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

func addCount(num int, counter chan &lt;- int) {
	time.Sleep(time.Second * 2)
    counter &lt;- num * 2
}

func main() {
    counter := make(chan int)
	int_slice := []int{2, 4}

    for _, item := range int_slice {
    	go addCount(item, counter)

    	fmt.Println(&lt;-counter)
	}
}

huangapple
  • 本文由 发表于 2017年7月7日 20:25:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/44970723.html
匿名

发表评论

匿名网友

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

确定