Golang,goroutines:panic:运行时错误:无效的内存地址

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

Golang, goroutines : panic: runtime error: invalid memory address

问题

我是你的中文翻译助手,以下是你提供的代码的翻译:

我在使用golang时还比较新手,正在尝试理解主要原则并编写基于通道的goroutine代码。

在我之前使用的其他语言中没有这样的工具,我想知道如何解决类似恐慌(panic)的错误...

我的代码:

package main

import "fmt"
import (
	"time"
)

type Work struct {
	x, y, z int
}

func worker(in <-chan *Work, out chan<- *Work) {
	for w := range in {
		w.z = w.x + w.y
		time.Sleep(time.Duration(w.z))
		out <- w
	}
}

func sendWork(in chan<- *Work) {
	var wo *Work
	wo.x, wo.y, wo.z = 1, 2, 3
	in <- wo
	in <- wo
	in <- wo
	in <- wo
	in <- wo
}

func receiveWork(out <-chan *Work) []*Work {
	var slice []*Work
	for el := range out {
		slice = append(slice, el)
	}
	return slice
}

func main() {
	in, out := make(chan *Work), make(chan *Work)
	for i := 0; i < 3; i++ {
		go worker(in, out)
	}

	go sendWork(in)

	data := receiveWork(out)

	fmt.Printf("%v", data)
}

但是在终端上我得到了这个错误:

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x401130]

goroutine 8 [running]:
main.sendWork(0xc0820101e0)
        C:/temp/gocode/src/helloA/helloA.go:21 +0x20
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:43 +0xe4

goroutine 1 [chan receive]:
main.receiveWork(0xc082010240, 0x0, 0x0, 0x0)
        C:/temp/gocode/src/helloA/helloA.go:31 +0x80
main.main()
        C:/temp/gocode/src/helloA/helloA.go:45 +0xf2

goroutine 5 [chan receive]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:12 +0x55
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

goroutine 6 [runnable]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:11
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

goroutine 7 [runnable]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:11
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

你如何确定问题出在哪里,以及如何优雅地关闭goroutine,而不是将它们留作进程...

附注:请原谅我这些初学者的问题。

英文:

I'm rather new in golang, and trying to understand main principles and write gouroutines based code, using chanels.

In others langs that i was using there were no such instruments, and i wonder getting such an errors like panic...

my code:

package main

import &quot;fmt&quot;
import (
	&quot;time&quot;
)
type Work struct {
	x,y,z int
}

func worker(in &lt;-chan *Work, out chan&lt;- *Work){
	for w := range in {
		w.z = w.x + w.y
		time.Sleep(time.Duration(w.z))
		out &lt;-w
	}
}

func sendWork(in chan &lt;- *Work){
	var wo *Work
	wo.x, wo.y, wo.z = 1,2,3
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
}

func receiveWork(out &lt;-chan *Work ) []*Work{
	var  slice []*Work
	for el := range out {
		slice = append(slice, el)
	}
	return slice
}

func main() {
	in, out := make(chan *Work), make(chan *Work)
	for i := 0; i&lt;3; i++{
		go worker(in, out)
	}

	go sendWork(in)

	data := receiveWork(out)

	fmt.Printf(&quot;%v&quot;, data)
}

But at the terminal i got this :

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x401130]

goroutine 8 [running]:
main.sendWork(0xc0820101e0)
        C:/temp/gocode/src/helloA/helloA.go:21 +0x20
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:43 +0xe4

goroutine 1 [chan receive]:
main.receiveWork(0xc082010240, 0x0, 0x0, 0x0)
        C:/temp/gocode/src/helloA/helloA.go:31 +0x80
main.main()
        C:/temp/gocode/src/helloA/helloA.go:45 +0xf2

goroutine 5 [chan receive]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:12 +0x55
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

goroutine 6 [runnable]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:11
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

goroutine 7 [runnable]:
main.worker(0xc0820101e0, 0xc082010240)
        C:/temp/gocode/src/helloA/helloA.go:11
created by main.main
        C:/temp/gocode/src/helloA/helloA.go:40 +0xaf

How can i determine where is the problem, and how can i close the gouroutines nicely, not to left them as processes...

p.s. Forgive me my noob questions. please

答案1

得分: 7

空指针解引用错误:
你试图访问一个由指针引用的结构体,但该指针尚未设置为该结构体的实例。你需要声明一个结构体,然后将指针指向它。

错误首次出现在这里:

wo.x, wo.y, wo.z = 1,2,3

你试图写入wo指向的对象。但是在这里指针是空的;它实际上没有指向Work的实例。我们需要创建该实例,以便我们可以指向它。

指向结构体的指针的空值是nil。如果你没有为它声明一个结构体实例,它就指向nil

var wo *Work

声明了一个类型为Work的指针wo,指向nil

 var wo = &Work{}

声明了一个类型为Work的指针wo,指向一个新的Work实例。

或者你可以使用更简洁的语法:

wo := &Work{}

至于死锁问题:
当我们关闭一个通道时,对该通道进行范围循环的操作将退出。在func worker中,我们对一个通道进行范围循环。当这个通道关闭时,工作线程将退出。

为了等待所有工作线程完成处理,我们使用sync.WaitGroup。这是一种在继续之前等待一组goroutine完成运行的简单方法。

首先,告诉等待组应该等待多少个goroutine。

wg.Add(3)

然后等待:

wg.Wait()

直到所有的goroutine都调用了

wg.Done()

当它们执行完毕时。

在这种情况下,当所有工作线程执行完毕时,我们需要关闭输出通道,以便func receiveWork可以退出其范围循环。我们可以通过为此任务启动一个新的goroutine来实现:

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

这是进行这些编辑后的完整文件:

package main

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

type Work struct {
	x, y, z int
}

func worker(in <-chan *Work, out chan<- *Work, wg *sync.WaitGroup) {
	for w := range in {
		w.z = w.x + w.y
		time.Sleep(time.Duration(w.z))
		out <- w
	}
	wg.Done() // 这个工作线程已经完成;让WaitGroup知道。
}

func sendWork(in chan<- *Work) {
	wo := &Work{x: 1, y: 2, z: 3} // 初始化结构体的更紧凑方式
	in <- wo
	in <- wo
	in <- wo
	in <- wo
	in <- wo
	close(in) // 我们发送到这个通道的操作完成;关闭它
}

func receiveWork(out <-chan *Work) []*Work {
	var slice []*Work
	for el := range out {
		slice = append(slice, el)
	}
	return slice
}

func main() {
	var wg sync.WaitGroup
	in, out := make(chan *Work), make(chan *Work)
	wg.Add(3) // 工作线程的数量
	for i := 0; i < 3; i++ {
		go worker(in, out, &wg)
	}

	go sendWork(in)

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

	data := receiveWork(out)

	fmt.Printf("%v", data)
}

输出结果为:

[0x104382f0 0x104382f0 0x104382f0 0x104382f0 0x104382f0]

这可能不是你期望的结果。但它确实突出了这段代码的一个问题。稍后再详细说明。

如果你想打印结构体的内容,你可以停止使用指向Work的指针,或者循环遍历切片的元素并逐个打印,像这样:

for _, w := range data {
	fmt.Printf("%v", w)
}

输出结果为:

&{1 2 3}&{1 2 3}&{1 2 3}&{1 2 3}&{1 2 3}

为了避免无限递归,Go在打印时不会跟随指针超过一层。所以你需要手动处理。

竞态条件:
由于你多次向通道发送指向相同*Work实例的指针,多个goroutine同时访问同一个实例而没有同步。你可能希望停止使用指针,而是使用值Work代替*Work

如果你想使用指针,可能是因为Work实际上非常大,你可能希望创建多个*Work实例,这样你只需要将其发送给一个goroutine。

这是关于代码的Go竞态检测器的结果:

C:/Go\bin\go.exe run -race C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go
[0xc0820403c0 0xc0820403c0 0xc0820403c0 0xc0820403c0 0xc0820403c0]==================
WARNING: DATA RACE
Write by goroutine 6:
  main.worker()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8a

Previous write by goroutine 8:
  main.worker()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8a

Goroutine 6 (running) created at:
  main.main()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:45 +0x10c

Goroutine 8 (running) created at:
  main.main()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:45 +0x10c
==================
Found 1 data race(s)
exit status 66

在这一行:

w.z = w.x + w.y

所有的goroutine都同时修改w.z,所以如果它们尝试向w.z写入不同的值,就无法确定实际上会有什么值。这个问题可以通过创建多个*Work实例或者使用值Work而不是指针来轻松解决。

英文:

The nil dereference:
You are trying to access a struct referenced by a pointer, but that pointer has not been set to an instance of that struct. You have to declare a struct that you can point the pointer to.

The error first appears here:

wo.x, wo.y, wo.z = 1,2,3

where you try to write to the object pointed to by wo. But the pointer is nil here; it's not actually pointing to an instance of Work. We have to create that instance, so we can point to it.

The nil value for a pointer to a struct is nil. If you don't declare a struct instance for it to point to, it points to nil.

var wo *Work

declares wo as a pointer of type Work to nil.

 var wo = &amp;Work{}

declares wo as a pointer of type Work to a new instance of Work.

Or you can use the shorter syntax:

wo := &amp;Work{}

As for the deadlock:

When we close a channel, range loops over that channel will exit. In func worker we range over a channel. When this channel is closed, the worker(s) will exit.

In order to wait for all the workers to finish processing, we use a sync.WaitGroup. This is an easy way to wait for a group of goroutines to finish running before continuing.

First you tell the waitgroup how many goroutines it should wait for.

wg.Add(3)

Then you wait:

wg.Wait()

until all of the goroutines have called

wg.Done()

which they do when they are done executing.

In this case, we need to close the output channel when all workers are done executing, so that the func receiveWork can exit its for range loop. We can do this by starting a new goroutine for this task:

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

This is the whole file, after these edits:

package main

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

type Work struct {
	x, y, z int
}

func worker(in &lt;-chan *Work, out chan&lt;- *Work, wg *sync.WaitGroup) {
	for w := range in {
		w.z = w.x + w.y
		time.Sleep(time.Duration(w.z))
		out &lt;- w
	}
	wg.Done() // this worker is now done; let the WaitGroup know.
}

func sendWork(in chan&lt;- *Work) {
	wo := &amp;Work{x: 1, y: 2, z: 3} // more compact way of initializing the struct
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
	in &lt;- wo
	close(in) // we are done sending to this channel; close it
}

func receiveWork(out &lt;-chan *Work) []*Work {
	var slice []*Work
	for el := range out {
		slice = append(slice, el)
	}
	return slice
}

func main() {
	var wg sync.WaitGroup
	in, out := make(chan *Work), make(chan *Work)
	wg.Add(3) // number of workers
	for i := 0; i &lt; 3; i++ {
		go worker(in, out, &amp;wg)
	}

	go sendWork(in)

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

	data := receiveWork(out)

	fmt.Printf(&quot;%v&quot;, data)
}

Which outputs:

[0x104382f0 0x104382f0 0x104382f0 0x104382f0 0x104382f0]

which is probably not what you expected. It does however highlight one problem with this code. More on that later.

If you want to print the contents of the structs, you could either stop using pointers to Work, or loop over the elements of the slice and print them one-by-one, like this:

for _, w := range data {
	fmt.Printf(&quot;%v&quot;, w)
}

which outputs:

&amp;{1 2 3}&amp;{1 2 3}&amp;{1 2 3}&amp;{1 2 3}&amp;{1 2 3}

Go doesn't follow pointers more than one step down when printing, to avoid infinite recursion, so you have to do this manually.

Race condition:

Since you are sending pointers to the same instance of *Work multiple times down the channel, that same instance is being accessed by multiple goroutines at the same time without synchronization. What you probably want is to stop using pointers, and use values. Work instead of *Work.

If you want to use pointers, maybe because Work is actually really big, you probably want to make multiple instances of *Work so you only ever send it to one goroutine.

Here's what the go race detector has to say about the code:

C:/Go\bin\go.exe run -race C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go
[0xc0820403c0 0xc0820403c0 0xc0820403c0 0xc0820403c0 0xc0820403c0]==================
WARNING: DATA RACE
Write by goroutine 6:
  main.worker()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8a

Previous write by goroutine 8:
  main.worker()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8a

Goroutine 6 (running) created at:
  main.main()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:45 +0x10c

Goroutine 8 (running) created at:
  main.main()
      C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:45 +0x10c
==================
Found 1 data race(s)
exit status 66

At this row:

w.z = w.x + w.y

all goroutines are modifying w.z concurrently, so if they try to write different values to w.z, there's no telling what value actually end up in there. Once again, this is easily fixed by creating multiple instances of *Work, or by using values instead of pointers: Work.

huangapple
  • 本文由 发表于 2016年2月5日 19:39:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/35223285.html
匿名

发表评论

匿名网友

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

确定