Go线程死锁错误 – 使用Go协程的正确方式是什么?

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

Go thread deadlock error - what is the correct way to use go routines?

问题

我正在编写一个程序,根据用户输入计算黎曼和。该程序将把函数分成1000个矩形(是的,我知道我还没有在其中加入数学),将它们相加并返回答案。我正在使用go routines来计算这1000个矩形,但是出现了一个致命错误:

fatal error: all go routines are asleep - deadlock!

处理多个go routines的正确方法是什么?我一直在寻找例子,但没有看到类似我这种情况的例子。我是新手,想要遵守标准。这是我的代码(如果你想看到这个典型用例的运行情况,它是可运行的,但会出错):

package main

import "fmt"
import "time"

//Data type to hold 'part' of function; ie. "4x^2"
type Pair struct {
	coef, exp int
}

//Calculates the y-value of a 'part' of the function and writes this to the channel
func calc(c *chan float32, p Pair, x float32) {
	val := x

	//Raise our x value to the power, contained in 'p'
	for i := 1; i < p.exp; i++ {

		val = val * val
	}

	//Read existing answer from channel
	ans := <-*c

	//Write new value to the channel
	*c <- float32(ans + (val * float32(p.coef)))
}

var c chan float32    //Channel
var m map[string]Pair //Map to hold function 'parts'

func main() {

	c = make(chan float32, 1001) //Buffered at 1001
	m = make(map[string]Pair)
	var counter int
	var temp_coef, temp_exp int
	var check string
	var up_bound, low_bound float32
	var delta float32

	counter = 1
	check = "default"

	//Loop through as long as we have no more function 'parts'
	for check != "n" {

		fmt.Print("Enter the coefficient for term ", counter, ": ")
		fmt.Scanln(&temp_coef)

		fmt.Print("Enter the exponent for term ", counter, ": ")
		fmt.Scanln(&temp_exp)

		fmt.Print("Do you have more terms to enter (y or n): ")
		fmt.Scanln(&check)

		fmt.Println("")

		//Put data into our map
		m[string(counter)] = Pair{temp_coef, temp_exp}

		counter++
	}

	fmt.Print("Enter the lower bound: ")
	fmt.Scanln(&low_bound)

	fmt.Print("Enter the upper bound: ")
	fmt.Scanln(&up_bound)

	//Calculate the delta; ie. our x delta for the riemann sum
	delta = (float32(up_bound) - float32(low_bound)) / float32(1000)

	//Make our go routines here to add
	for i := low_bound; i < up_bound; i = i + delta {

		//'counter' is indicative of the number of function 'parts' we have
		for j := 1; j < counter; j++ {

			//Go routines made here
			go calc(&c, m[string(j)], i)
		}

	}

	//Wait for the go routines to finish
	time.Sleep(5000 * time.Millisecond)

	//Read the result?
	ans := <-c

	fmt.Print("Answer: ", ans)
}
英文:

I am writing a program that calculates a Riemann sum based on user input. The program will split the function into 1000 rectangles (yes I know I haven't gotten that math in there yet) and sum them up and return the answer. I am using go routines to compute the 1000 rectangles but am getting an

fatal error: all go routines are asleep - deadlock!

What is the correct way to handle multiple go routines? I have been looking around and haven't seen an example that resembles my case? I'm new and want to adhere to standards. Here is my code (it is runnable if you'd like to see what a typical use case of this is - however it does break)

package main
import &quot;fmt&quot;
import &quot;time&quot;
//Data type to hold &#39;part&#39; of function; ie. &quot;4x^2&quot;
type Pair struct {
coef, exp int
}
//Calculates the y-value of a &#39;part&#39; of the function and writes this to the channel
func calc(c *chan float32, p Pair, x float32) {
val := x
//Raise our x value to the power, contained in &#39;p&#39;
for i := 1; i &lt; p.exp; i++ {
val = val * val
}
//Read existing answer from channel
ans := &lt;-*c
//Write new value to the channel
*c &lt;- float32(ans + (val * float32(p.coef)))
}
var c chan float32    //Channel
var m map[string]Pair //Map to hold function &#39;parts&#39;
func main() {
c = make(chan float32, 1001) //Buffered at 1001
m = make(map[string]Pair)
var counter int
var temp_coef, temp_exp int
var check string
var up_bound, low_bound float32
var delta float32
counter = 1
check = &quot;default&quot;
//Loop through as long as we have no more function &#39;parts&#39;
for check != &quot;n&quot; {
fmt.Print(&quot;Enter the coefficient for term &quot;, counter, &quot;: &quot;)
fmt.Scanln(&amp;temp_coef)
fmt.Print(&quot;Enter the exponent for term &quot;, counter, &quot;: &quot;)
fmt.Scanln(&amp;temp_exp)
fmt.Print(&quot;Do you have more terms to enter (y or n): &quot;)
fmt.Scanln(&amp;check)
fmt.Println(&quot;&quot;)
//Put data into our map
m[string(counter)] = Pair{temp_coef, temp_exp}
counter++
}
fmt.Print(&quot;Enter the lower bound: &quot;)
fmt.Scanln(&amp;low_bound)
fmt.Print(&quot;Enter the upper bound: &quot;)
fmt.Scanln(&amp;up_bound)
//Calculate the delta; ie. our x delta for the riemann sum
delta = (float32(up_bound) - float32(low_bound)) / float32(1000)
//Make our go routines here to add
for i := low_bound; i &lt; up_bound; i = i + delta {
//&#39;counter&#39; is indicative of the number of function &#39;parts&#39; we have
for j := 1; j &lt; counter; j++ {
//Go routines made here
go calc(&amp;c, m[string(j)], i)
}
}
//Wait for the go routines to finish
time.Sleep(5000 * time.Millisecond)
//Read the result?
ans := &lt;-c
fmt.Print(&quot;Answer: &quot;, ans)
}

答案1

得分: 5

它发生死锁是因为calc()main()函数都在任何人写入通道之前从通道中读取。

因此,每个(非主)goroutine都会在以下位置阻塞:

ans := <-c

等待其他goroutine将值输入通道。因此,它们都无法进入下一行,即实际写入通道的位置。而main()例程将在以下位置阻塞:

ans := <-c

每个人都在等待,形成了死锁。

使用带缓冲的通道

你的解决方案应该让calc()函数只写入通道,而main()函数可以在for-range循环中从通道中读取值,并对来自goroutine的值进行求和。

你还需要添加一种方法让main()知道何时不会再有更多的值到达,可以使用sync.WaitGroup(也许不是最好的方法,因为main不应该等待而是对结果进行求和)或者一个普通的计数器。

使用共享内存

有时候你不一定需要通道。使用一个共享的值,你可以使用sync/atomic包(原子加法不能用于浮点数)或者使用sync.Mutex进行加锁也可以。

英文:

It dead locks because both the calc() and the main() function reads from the channel before anyone gets to write to it.

So you will end up having every (non-main) go routine blocking at:

ans := &lt;-*c

waiting for someone other go routine to enter a value into the channel. There fore none of them gets to the next line where they actually write to the channel. And the main() routine will block at:

ans := &lt;-c

Everyone is waiting = deadlock

Using buffered channels

Your solution should have the calc() function only writing to the channel, while the main() could read from it in a for-range loop, suming up the values coming from the go-routines.

You will also need to add a way for main() to know when there will be no more values arriving, perhaps by using a sync.WaitGroup (maybe not the best, since main isn't suppose to wait but rather sum things up) or an ordinary counter.

Using shared memory

Sometimes it is not necessarily a channel you need. Having a shared value that you <strike>update with the sync/atomic package</strike> (atomic add doesn't work on floats) lock with a sync.Mutex works fine too.

huangapple
  • 本文由 发表于 2014年5月8日 08:12:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/23530611.html
匿名

发表评论

匿名网友

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

确定