2016-02-05 146 views
4

我在golang中比較新,並試圖理解主要原則並使用chanels編寫基於gouroutines的代碼。Golang,goroutines:panic:運行時錯誤:無效的內存地址

在其他LANGS,我用沒有這樣的儀器,我不知道像越來越恐慌這樣的錯誤...

我的代碼:

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.原諒我的noob問題。請

回答

6

的無解引用:
您試圖訪問一個指針引用一個結構,但指針沒有被設置爲結構的實例。你必須聲明一個可以指向指針的結構。

錯誤第一次出現在這裏:

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

在您嘗試寫入對象通過wo指向。但指針在這裏沒有;它實際上並不指向Work的實例。我們必須創建這個實例,所以我們可以指出它。

指向結構體的指針的nil值爲nil。如果你沒有聲明一個指向它的結構實例,它指向nil。

var wo *Work 

聲明wo作爲Work類型的指針nil

var wo = &Work{} 

聲明woWork類型的指針的Work一個新的實例。

或者你可以使用更短的語法:

wo := &Work{} 

至於僵局:

當我們關閉通道,範圍循環通過該信道將退出。在func worker我們通過頻道進行排序。當這個頻道關閉時,工作人員將退出。

爲了等待所有工人完成處理,我們使用了sync.WaitGroup。這是一個簡單的方法來等待一組goroutines在繼續之前完成運行。

首先你告訴waitgroup它應該等待多少個goroutines。

wg.Add(3) 

那你就等着:

wg.Wait() 

,直到所有的夠程都要求

wg.Done() 

它們執行完畢時,他們這樣做。

在這種情況下,我們需要關閉輸出通道時,所有的工人都執行完,從而使func receiveWork可以退出其範圍內循環。

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] 

這可能是我們可以開始一個新的goroutine這個任務做到這一點不是你所期望的。但是,它強調了這個代碼的一個問題。稍後更多。

如果你要打印的結構的內容,您既可以停止使用指針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 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 

所有夠程併發修改w.z,所以如果他們嘗試不同的值寫入w.z ,但沒有說明實際上最終會在哪裏實現。 Work:再次,這是很容易通過創建的*Work多個實例,或者通過使用值而不是指針的固定。

相關問題