Golang,goroutines:恐慌:运行时错误:内存地址无效

Alt*_*ion 4 channel go goroutine web

我是golang的新手,并试图理解主要原则并使用chanels编写基于gouroutines的代码.

在我使用的其他语言中没有这样的乐器,我想知道这样的错误就像恐慌......

我的代码:

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)
}
Run Code Online (Sandbox Code Playgroud)

但在终端我得到了这个:

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
Run Code Online (Sandbox Code Playgroud)

我如何确定问题的位置,以及如何很好地关闭gouroutines,而不是将它们作为进程...

ps原谅我的菜鸟问题.请

Fil*_*und 6

nil dereference:
您正在尝试访问由指针引用的结构,但该指针尚未设置为该结构的实例.您必须声明一个可以指向指针的结构.

错误首先出现在这里:

wo.x, wo.y, wo.z = 1,2,3
Run Code Online (Sandbox Code Playgroud)

在哪里尝试写入指向的对象wo.但指针在这里是零; 它实际上并没有指向一个实例Work.我们必须创建该实例,因此我们可以指出它.

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

var wo *Work
Run Code Online (Sandbox Code Playgroud)

声明wo为类型的指针Worknil.

 var wo = &Work{}
Run Code Online (Sandbox Code Playgroud)

声明woWork新实例的类型指针Work.

或者您可以使用较短的语法:

wo := &Work{}
Run Code Online (Sandbox Code Playgroud)

至于僵局:

当我们关闭一个频道时,该频道上的范围循环将退出.在func worker我们的范围通过信道.当此通道关闭时,工人将退出.

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

首先告诉waitgroup应该等待多少个goroutine.

wg.Add(3)
Run Code Online (Sandbox Code Playgroud)

然后你等一下:

wg.Wait()
Run Code Online (Sandbox Code Playgroud)

直到所有的goroutines都打电话给你

wg.Done()
Run Code Online (Sandbox Code Playgroud)

当他们完成执行时他们会这样做.

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

go func() {
    wg.Wait()
    close(out)
}()
Run Code Online (Sandbox Code Playgroud)

在完成以下编辑后,这是整个文件:

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)
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

[0x104382f0 0x104382f0 0x104382f0 0x104382f0 0x104382f0]
Run Code Online (Sandbox Code Playgroud)

这可能不是你所期望的.但它确实强调了此代码的一个问题.稍后会详细介绍.

如果要打印结构体的内容,可以停止使用指针Work,或者循环切片的元素并逐个打印,如下所示:

for _, w := range data {
    fmt.Printf("%v", w)
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

&{1 2 3}&{1 2 3}&{1 2 3}&{1 2 3}&{1 2 3}
Run Code Online (Sandbox Code Playgroud)

Go在打印时不会超过一步指针,以避免无限递归,因此您必须手动执行此操作.

比赛条件:

由于您*Work在通道下多次向同一个实例发送指针,因此多个goroutine同时访问同一个实例而没有同步.你可能想要的是停止使用指针,并使用值.Work而不是*Work.

如果你想使用指针,也许因为Work它实际上非常大,你可能想制作多个实例,*Work所以你只需要将它发送给一个goroutine.

以下是赛车探测器对代码的评价:

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
Run Code Online (Sandbox Code Playgroud)

在这一行:

w.z = w.x + w.y
Run Code Online (Sandbox Code Playgroud)

所有的goroutine都在w.z同时修改,所以如果他们试图写出不同的值w.z,那就不知道实际上最终会有什么价值.再次,通过创建多个实例*Work或使用值而不是指针来轻松修复此问题:Work.