Golang, goroutines: паника: ошибка выполнения: неверный адрес памяти



Я довольно новичок в golang, и пытаюсь понять основные принципы и написать код на основе gouroutines, используя chanels.



В других языках, которые я использовал, не было таких инструментов, и я удивляюсь, получая такие ошибки, как паника...



Мой код:



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. Прости меня, мой нуб. вопросы. пожалуйста

704   1  

1 ответ:

Нулевое разыменование:
Вы пытаетесь получить доступ к структуре, на которую ссылается указатель, но этот указатель не был установлен на экземпляр этой структуры. Необходимо объявить структуру, на которую можно указать указатель.

Ошибка сначала появляется здесь:

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

Где вы пытаетесь записать объект, на который указывает wo. Но указатель здесь равен нулю; на самом деле он не указывает на экземпляр Work. Мы должны создать этот экземпляр, чтобы мы могли указать на оно.

Нулевое значение для указателя на структуру равно nil. Если вы не объявляете экземпляр структуры, на который он указывает, он указывает на ноль.

var wo *Work

Объявляет wo как указатель типа Work на nil.

 var wo = &Work{}

Объявляет wo как указатель типа Work на новый экземпляр Work.

Или вы можете использовать более короткий синтаксис:

wo := &Work{}

Что касается тупика:

Когда мы закрываем канал, петли диапазона над этим каналом будут выходить. В func worker мы колеблемся над каналом. Когда этот канал закрыт, работник(ы) выйдет.

Чтобы дождаться, пока все рабочие закончат обработку, мы используем sync.WaitGroup. Это простой способ дождаться, пока группа горотинов закончит бег, прежде чем продолжить.

Сначала вы говорите группе ожидания, сколько goroutines она должна ждать.

wg.Add(3)

Тогда вы ждете:

wg.Wait()

Пока все горотины не позовут

wg.Done()
Что они и делают, когда заканчивают работу. проведение.

В этом случае нам нужно закрыть выходной канал, когда все рабочие закончат выполнение, так что 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 несколько раз вниз по каналу, этот же экземпляр является доступ к нескольким горотинам одновременно без синхронизации. Вероятно, вы хотите прекратить использование указателей и использовать значения. Work вместо *Work.

Если вы хотите использовать указатели, возможно, потому, что Work на самом деле очень большой, вы, вероятно, хотите сделать несколько экземпляров *Work, так что вы всегда отправляете его только одному goroutine.

Вот что должен сказать о коде детекторgo race :

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.

Comments

    Ничего не найдено.