我对golang相当陌生,并尝试使用chanel理解主要原理并编写基于古洛丁的代码。
在我使用的其他语言中,没有这样的工具,我不知道会出现这样的错误,例如恐慌…
我的代码:
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
我如何确定问题出在哪里,如何很好地关闭古丁鱼,而不是将其留在流程中…
ps原谅我我的菜鸟问题。请
nil取消引用: 您正在尝试访问指针所引用的结构,但是该指针尚未设置为该结构的实例。您必须声明一个可以指向指针的结构。
错误首先出现在这里:
wo.x, wo.y, wo.z = 1,2,3
您尝试在其中写入由指向的对象的地方wo。但是这里的指针为零。它实际上并不指向的实例Work。我们必须创建该实例,所以我们可以指向它。
wo
Work
指向结构体的指针的nil值为nil。如果未声明要指向的结构实例,则它指向nil。
nil
var wo *Work
声明wo为类型的指针Work到nil。
var wo = &Work{}
声明wo为指向的Work新实例的类型的指针Work。
或者,您可以使用较短的语法:
wo := &Work{}
至于僵局:
当我们关闭一个通道时,该通道上的范围循环将退出。在func worker我们范围内的渠道。关闭此通道后,工作人员将退出。
func worker
为了等待所有工人完成处理,我们使用sync.WaitGroup。这是等待一组goroutine完成运行然后再继续的简单方法。
sync.WaitGroup
首先,您告诉等待组它应该等待多少个goroutine。
wg.Add(3)
然后,您等待:
wg.Wait()
直到所有goroutine都调用了
wg.Done()
完成执行后会执行的操作。
在这种情况下,我们需要在所有工作程序执行完毕后关闭输出通道,以便func receiveWork可以退出其for range循环。我们可以通过为此任务启动一个新的goroutine来做到这一点:
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]
这可能不是您所期望的。但是,它的确突出了此代码的一个问题。以后再说。
如果要打印结构的内容,则可以停止使用指向的指针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它确实很大,则可能要创建多个实例,*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 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。
w.z