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