英文:
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 "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)
}
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 = &Work{}
declares wo
as a pointer of type Work
to a new instance of Work
.
Or you can use the shorter syntax:
wo := &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 (
"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() // this worker is now done; let the WaitGroup know.
}
func sendWork(in chan<- *Work) {
wo := &Work{x: 1, y: 2, z: 3} // more compact way of initializing the struct
in <- wo
in <- wo
in <- wo
in <- wo
in <- wo
close(in) // we are done sending to this channel; close it
}
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) // number of workers
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)
}
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("%v", w)
}
which outputs:
&{1 2 3}&{1 2 3}&{1 2 3}&{1 2 3}&{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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论