零解引用:
您正在尝试访问由指针引用的结构,但该指针尚未设置为该结构的实例。您必须声明一个可以指向指针的结构。
错误首先出现在这里:
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
.