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

问题描述:

我在golang中比较新,并试图理解主要原则并使用chanels编写基于gouroutines的代码。Golang,goroutines:panic:运行时错误:无效的内存地址

在其他LANGS,我用没有这样的仪器,我不知道像越来越恐慌这样的错误...

我的代码:

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.原谅我的noob问题。请

的无解引用:
您试图访问一个指针引用一个结构,但指针没有被设置为结构的实例。你必须声明一个可以指向指针的结构。

错误第一次出现在这里:

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

在您尝试写入对象通过wo指向。但指针在这里没有;它实际上并不指向Work的实例。我们必须创建这个实例,所以我们可以指出它。

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

var wo *Work 

声明wo作为Work类型的指针nil

var wo = &Work{} 

声明woWork类型的指针的Work一个新的实例。

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

wo := &Work{} 

至于僵局:

当我们关闭通道,范围循环通过该信道将退出。在func worker我们通过频道进行排序。当这个频道关闭时,工作人员将退出。

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

首先你告诉waitgroup它应该等待多少个goroutines。

wg.Add(3) 

那你就等着:

wg.Wait() 

,直到所有的够程都要求

wg.Done() 

它们执行完毕时,他们这样做。

在这种情况下,我们需要关闭输出通道时,所有的工人都执行完,从而使func receiveWork可以退出其范围内循环。

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] 

这可能是我们可以开始一个新的goroutine这个任务做到这一点不是你所期望的。但是,它强调了这个代码的一个问题。稍后更多。

如果你要打印的结构的内容,您既可以停止使用指针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多次下降通道相同的情况下,同一实例是由多个在够程不同步的相同时间被访问。你可能想要的是停止使用指针,并使用值。 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 

所有够程并发修改w.z,所以如果他们尝试不同的值写入w.z ,但没有说明实际上最终会在哪里实现。 Work:再次,这是很容易通过创建的*Work多个实例,或者通过使用值而不是指针的固定。