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

2024-02-08

我对 golang 相当陌生,并试图理解主要原理并使用通道编写基于 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

我如何确定问题出在哪里,以及如何很好地关闭 goroutine,而不是将它们保留为进程......

附注请原谅我的菜鸟问题。请


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

错误首先出现在这里:

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

您尝试写入指向的对象的位置wo。但这里的指针为零;它实际上并不是指向一个实例Work。我们必须创建该实例,以便我们可以指向它。

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

var wo *Work

宣称wo作为类型的指针Work to nil.

 var wo = &Work{}

宣称wo作为类型的指针Work到一个新实例Work.

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

wo := &Work{}

至于僵局:

当我们关闭一个通道时,该通道上的范围循环将退出。在func worker我们跨越一个渠道。当该通道关闭时,工作人员将退出。

为了等待所有worker完成处理,我们使用一个sync.WaitGroup。这是等待一组 goroutine 完成运行然后再继续运行的简单方法。

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

wg.Add(3)

然后你就等着:

wg.Wait()

直到所有 goroutine 都调用完毕

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。

这是什么去比赛探测器 https://blog.golang.org/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

所有 goroutine 都在修改w.z同时,所以如果他们尝试写入不同的值w.z,没有人知道其中最终会产生什么价值。再次,通过创建多个实例可以轻松解决此问题*Work,或者使用值代替指针:Work.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • IF 语句中 OR 和 AND 运算符的中断条件

    If 语句和任何其他布尔比较足够智能 可以在评估时在第一个 FALSE 值处停止A and B and C and D评估时首先为 TRUE 值A or B or C or D 这种行为的名称是什么 这是编译器优化吗 如果是这样 有没有办法
  • 如何强制完全下载链接上的txt文件?

    我有一个简单的文本文件 我想在任何锚标记链接上下载该文件 但是当我点击该链接时 txt 文件显示给我但未下载 我已经尝试过这段代码 a href test txt Click here a 单击链接时下载文件 而不是导航到文件 a href
  • 路由错误未初始化常量用户

    我是 Rails 新手 我正在尝试为演示应用程序设置 使用 facebook 登录 我正在使用 OmniAuth 并遵循本教程 https github com plataformatec devise wiki OmniAuth 概述 h
  • 多类模型的准确率、精确度和召回率

    我该如何计算accuracy 精确 and recall对于混淆矩阵中的每个类 我正在使用嵌入式数据集 iris 混淆矩阵如下 prediction setosa versicolor virginica setosa 29 0 0 ver
  • 打印 Windows 窗体

    我继承了一些代码来打印表单的内容 但是在纸上生成的图像似乎有某种阴影 模糊 就好像它试图进行抗锯齿但做得不太好 并且字母在边缘像素化 有谁知道提高最终质量的方法吗 System Drawing Printing PrintDocument
  • Visual Studio 扩展中的 app.config?

    我创建了一个代表 Visual Studio 项目向导 vsix 包 的 Visual Studio 扩展 我正在尝试连接 log4net 但没有成功 我已将问题归结为 app config 未正确加载 我已将其添加到我的 Visual S
  • 棘手的数组初始化

    在 C 不是 C 中 我尝试创建两个包含相同值的字符串表 但以两种不同的方式对值进行排序 而且我不希望字符串在内存中重复 基本上 我想做以下事情 除了根据 gcc 之外 它会失败 因为第二个数组初始化中的 初始化器元素不是常量 有办法解决这
  • 在 WCF/.NET 中返回数据表

    我有一个 WCF 服务 我想从中返回一个数据表 我知道 就返回 DataTable 是否是一个好的实践而言 这通常是一个备受争议的话题 让我们暂时把它放在一边 当我从头开始创建数据表时 如下所示 没有任何问题 该表已创建 填充并返回给客户端
  • 删除工作表(如果存在)并创建一个新工作表

    我想浏览我的 Excel 工作表并找到具有特定名称的工作表 如果找到则删除该工作表 之后 我想在所有具有该名称的现有工作表之后创建一个工作表 我的代码如下 For Each ws In Worksheets If ws Name asdf
  • Scapy生成STP(生成树协议)数据包

    我正在尝试生成STP数据包并使用wireshark捕获它 基本上我所做的是 gt gt gt 从Scapy发送 STP wireshark的结果是 53918 2671 938356000 00 00 00 00 00 00 FC 49 格
  • 启用 NoResize 模式时最小化窗口

    我有一个 WPF 应用程序 其中Window财产ResizeMode设置为NoResize 因此最大化和最小化按钮被隐藏 有没有办法添加最小化按钮 因为我不想允许用户仅调整窗口大小以避免表单上的控件变形 但最小化窗口是一个有用的功能 Set
  • 如何让按钮在div中居中?

    我有一个宽度为 100 的 div 我想在其中放置一个按钮 我该怎么做 div style width 100 height 100 border 1px solid div
  • 隐藏标题页上的幻灯片编号

    我在用 Reveal initialize slideNumber true 是否有可能隐藏标题页上的幻灯片编号 我是这样做的 Reveal addEventListener slidechanged event gt const isFi
  • 标签内的图像
  • 我有一个关于内部浮动元素的问题 li tag 我有以下标记 li li img src concept truck jpg alt 2013 Toyota Tacoma p 2013 Toyota Tacoma p p Price 4500
  • 无法获取 Flask 应用程序中设置的环境变量

    我尝试在 CentOS 中将敏感信息设置为环境变量 并将它们传递给主文件中使用的 Flask 配置文件 即init py 但这没有用 Flask 应用程序在 Apache 下运行 我首先以 root 用户身份编辑 etc environme
  • 使用 LLVM C API 生成对内部函数的调用

    我正在编写一些使用 LLVM C API 的代码 如何使用内在函数 例如llvm cos f64 or llvm sadd with overflow i32 每当我尝试通过生成一个全局来做到这一点LLVMAddGlobal 具有正确的类型
  • 如何使用ExternalTask​​Sensor触发Airflow DAG独立运行

    我构建了两个 DAG dag a dag b 并在 dag b 中创建了一个刺探 dag a 的ExternalTask Sensor 这些 DAG 有两个用例 同时调度dag a和dag b 并使用依赖关系先处理dag a 然后处理dag
  • SwiftUI 2 弹出到根视图,没有场景委托

    我想在用户注销时刷新根视图 但我找不到如何在新的无场景 SwiftUi 2 下执行此操作 Next is 瑞士马克 https stackoverflow com questions 63461933 swiftui how to chan
  • 将纬度和经度转换为十进制值

    我的 GPS 信息以以下形式呈现 北纬 36 57 9 西经 110 4 21 我可以使用Chris Veness 的 javascript 函数 http www movable type co uk scripts latlong ht
  • Golang,goroutines:恐慌:运行时错误:无效的内存地址

    我对 golang 相当陌生 并试图理解主要原理并使用通道编写基于 gouroutines 的代码 在我使用的其他语言中没有这样的工具 我想知道是否会出现诸如恐慌之类的错误 我的代码 package main import fmt impo