funcFib(loader *Loader, key string) any { n, _ := strconv.Atoi(key) if n == 0 { return0 } if n == 1 { return1 } children := []any{strconv.Itoa(n - 1), strconv.Itoa(n - 2)} value_susps := make([]any, len(children)) for i, child := range children { value_susps[i] = loader.Load(Fib, child.(string)) } returnfunc() any { values := Await(value_susps).([]any) var res int for _, v := range values { res += v.(int) } return res } }
funcAwait(root any) any { var result any q := []func() any{func() any { v := unwrap(root) result = v return v }} forlen(q) > 0 { f := q[0] q = q[1:] switch value := unwrap(f).(type) { case []any: for i, elem := range value { sub_i := i sub_susp := elem q = append(q, func() any { v := unwrap(sub_susp) value[sub_i] = v return v }) } default: } } return result }
Go 是一个有 GC 的语言,这意味着非空指针指向的内存都是能用的。
再加上不用unsafe包, Go 是不允许你做指针运算的,所以不应该出现访问非法地址的错误。
按这个思路,unwrap 里读到的 susp ,可能是 type 为 func() ,value 却不是函数指针的脏数据。
按图索骥
回过头来再看调用栈,导致 panic 的 unwrap 调用来自 Await 这部分代码:
1 2 3 4 5 6 7 8 9
for i, elem := range value { // <- read fib.go:43 sub_i := i sub_susp := elem q = append(q, func() any { v := unwrap(sub_susp) // <- panic value[sub_i] = v // <- write fib.go:48 return v }) }
如果说是 data race 造成了 sub_susp 变脏数据,那也吻合这里对 []any 数组元素的修改。
$ go test -race ./fib ================== WARNING: DATA RACE Read at 0x00c000076690 by goroutine 33: kkkiio.io/algo/fib.Await() /home/kkkiio/projects/algo/fib/fib.go:43 +0x3f1 ... ... Previous write at 0x00c000076690 by goroutine 45: kkkiio.io/algo/fib.Await.func2() /home/kkkiio/projects/algo/fib/fib.go:48 +0xb