Golang, goroutines : panic: runtime error: invalid memory address
Golang, goroutines : panic: runtime error: invalid memory address
我是 golang 的新手,正在尝试理解主要原理并使用 chanels 编写基于 gouroutines 的代码。
在我使用的其他语言中没有这样的工具,我想知道会出现像 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
我怎样才能确定问题出在哪里,我怎样才能很好地关闭 gouroutines,而不是让它们作为进程...
p.s。请原谅我的菜鸟问题。请
零解引用:
您正在尝试访问指针引用的结构,但该指针尚未设置为该结构的实例。您必须声明一个可以将指针指向的结构。
错误首先出现在这里:
wo.x, wo.y, wo.z = 1,2,3
您尝试写入 wo
指向的对象的位置。但是这里的指针是零;它实际上并没有指向 Work
的实例。我们必须创建那个实例,这样我们才能指向它。
指向结构的指针的 nil 值为 nil
。如果你不声明一个结构实例让它指向,它指向 nil。
var wo *Work
将 wo
声明为指向 nil
的 Work
类型的指针。
var wo = &Work{}
将 wo
声明为指向 Work
的新实例的 Work
类型的指针。
或者您可以使用更短的语法:
wo := &Work{}
至于死锁:
当我们关闭一个通道时,该通道上的范围循环将退出。在 func worker
中,我们在一个通道上进行测距。当此通道关闭时,worker(s) 将退出。
为了等待所有worker完成处理,我们使用了一个sync.WaitGroup
。这是在继续之前等待一组 goroutines 完成 运行 的简单方法。
首先你告诉等待组它应该等待多少个 goroutines。
wg.Add(3)
那你稍等:
wg.Wait()
直到所有的 goroutines 都调用了
wg.Done()
他们在完成执行后执行的操作。
在这种情况下,我们需要在所有 worker 完成执行后关闭输出通道,以便 func receiveWork
可以退出其 for range 循环。我们可以通过为这个任务启动一个新的 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() // 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)
}
输出:
[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 race detector 对代码的评价:
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
所有 goroutines 都在同时修改 w.z
,所以如果他们尝试向 w.z
写入不同的值,则无法说明实际最终的值是什么。再一次,这很容易通过创建 *Work
的多个实例或使用值而不是指针来解决:Work
.
我是 golang 的新手,正在尝试理解主要原理并使用 chanels 编写基于 gouroutines 的代码。
在我使用的其他语言中没有这样的工具,我想知道会出现像 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
我怎样才能确定问题出在哪里,我怎样才能很好地关闭 gouroutines,而不是让它们作为进程...
p.s。请原谅我的菜鸟问题。请
零解引用:
您正在尝试访问指针引用的结构,但该指针尚未设置为该结构的实例。您必须声明一个可以将指针指向的结构。
错误首先出现在这里:
wo.x, wo.y, wo.z = 1,2,3
您尝试写入 wo
指向的对象的位置。但是这里的指针是零;它实际上并没有指向 Work
的实例。我们必须创建那个实例,这样我们才能指向它。
指向结构的指针的 nil 值为 nil
。如果你不声明一个结构实例让它指向,它指向 nil。
var wo *Work
将 wo
声明为指向 nil
的 Work
类型的指针。
var wo = &Work{}
将 wo
声明为指向 Work
的新实例的 Work
类型的指针。
或者您可以使用更短的语法:
wo := &Work{}
至于死锁:
当我们关闭一个通道时,该通道上的范围循环将退出。在 func worker
中,我们在一个通道上进行测距。当此通道关闭时,worker(s) 将退出。
为了等待所有worker完成处理,我们使用了一个sync.WaitGroup
。这是在继续之前等待一组 goroutines 完成 运行 的简单方法。
首先你告诉等待组它应该等待多少个 goroutines。
wg.Add(3)
那你稍等:
wg.Wait()
直到所有的 goroutines 都调用了
wg.Done()
他们在完成执行后执行的操作。
在这种情况下,我们需要在所有 worker 完成执行后关闭输出通道,以便 func receiveWork
可以退出其 for range 循环。我们可以通过为这个任务启动一个新的 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() // 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)
}
输出:
[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 race detector 对代码的评价:
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
所有 goroutines 都在同时修改 w.z
,所以如果他们尝试向 w.z
写入不同的值,则无法说明实际最终的值是什么。再一次,这很容易通过创建 *Work
的多个实例或使用值而不是指针来解决:Work
.