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

我对 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)

}

但在终端我得到了这个:


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,而不是将它们作为进程...


开满天机
浏览 128回答 1
1回答

陪伴而非守候

nil 取消引用:您正在尝试访问由指针引用的结构,但该指针尚未设置为该结构的实例。您必须声明一个可以指向指针的结构。错误首先出现在这里:wo.x, wo.y, wo.z = 1,2,3您尝试写入指向的对象的位置wo。但是这里的指针为零;它实际上并不是指向Work. 我们必须创建该实例,以便我们可以指向它。指向结构体的指针的 nil 值为nil。如果你没有为它声明一个结构体实例,它指向 nil。var wo *Work声明wo为类型的指针Work到nil。&nbsp;var wo = &Work{}声明wo为指向 的Work新实例的类型指针Work。或者您可以使用较短的语法:wo := &Work{}至于死锁:当我们关闭一个通道时,该通道上的范围循环将退出。在func worker我们范围内的一个频道。当这个通道关闭时,worker(s) 将退出。为了等待所有工人完成处理,我们使用sync.WaitGroup. 这是在继续之前等待一组 goroutine 完成运行的简单方法。首先你告诉等待组它应该等待多少个 goroutine。wg.Add(3)然后你等待:wg.Wait()直到所有的 goroutine 都调用了wg.Done()当他们完成执行时他们会这样做。在这种情况下,我们需要在所有 worker 执行完毕后关闭输出通道,以便func receiveWork可以退出其 for range 循环。我们可以通过为此任务启动一个新的 goroutine 来做到这一点:go func() {&nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; close(out)}()这是整个文件,经过这些编辑:package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "sync"&nbsp; &nbsp; "time")type Work struct {&nbsp; &nbsp; x, y, z int}func worker(in <-chan *Work, out chan<- *Work, wg *sync.WaitGroup) {&nbsp; &nbsp; for w := range in {&nbsp; &nbsp; &nbsp; &nbsp; w.z = w.x + w.y&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(time.Duration(w.z))&nbsp; &nbsp; &nbsp; &nbsp; out <- w&nbsp; &nbsp; }&nbsp; &nbsp; wg.Done() // this worker is now done; let the WaitGroup know.}func sendWork(in chan<- *Work) {&nbsp; &nbsp; wo := &Work{x: 1, y: 2, z: 3} // more compact way of initializing the struct&nbsp; &nbsp; in <- wo&nbsp; &nbsp; in <- wo&nbsp; &nbsp; in <- wo&nbsp; &nbsp; in <- wo&nbsp; &nbsp; in <- wo&nbsp; &nbsp; close(in) // we are done sending to this channel; close it}func receiveWork(out <-chan *Work) []*Work {&nbsp; &nbsp; var slice []*Work&nbsp; &nbsp; for el := range out {&nbsp; &nbsp; &nbsp; &nbsp; slice = append(slice, el)&nbsp; &nbsp; }&nbsp; &nbsp; return slice}func main() {&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; in, out := make(chan *Work), make(chan *Work)&nbsp; &nbsp; wg.Add(3) // number of workers&nbsp; &nbsp; for i := 0; i < 3; i++ {&nbsp; &nbsp; &nbsp; &nbsp; go worker(in, out, &wg)&nbsp; &nbsp; }&nbsp; &nbsp; go sendWork(in)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; &nbsp; &nbsp; close(out)&nbsp; &nbsp; }()&nbsp; &nbsp; data := receiveWork(out)&nbsp; &nbsp; fmt.Printf("%v", data)}哪些输出:[0x104382f0 0x104382f0 0x104382f0 0x104382f0 0x104382f0]这可能不是您所期望的。然而,它确实突出了此代码的一个问题。稍后再谈。如果你想打印结构体的内容,你可以停止使用指向 的指针Work,或者遍历切片的元素并逐个打印它们,如下所示:for _, w := range data {&nbsp; &nbsp; 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。以下是围棋比赛检测器对代码的评价: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 RACEWrite by goroutine 6:&nbsp; main.worker()&nbsp; &nbsp; &nbsp; C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8aPrevious write by goroutine 8:&nbsp; main.worker()&nbsp; &nbsp; &nbsp; C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:15 +0x8aGoroutine 6 (running) created at:&nbsp; main.main()&nbsp; &nbsp; &nbsp; C:/gopath/src/github.com/drathier/scratchpad/go/main/main.go:45 +0x10cGoroutine 8 (running) created at:&nbsp; main.main()&nbsp; &nbsp; &nbsp; 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。
打开App,查看更多内容
随时随地看视频慕课网APP