我开始在官方网站上浏览Tour of Go来玩Go。
我只有编程方面的基本经验,但在进入频道页面时,我开始玩游戏试图了解它并且我最终感到非常困惑。
这就是我的代码:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
s[0] = 8
s = append(s, 20)
fmt.Println(s)
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
fmt.Println(s[0])
go sum(s[len(s)/2:], c)
fmt.Println(s)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
fmt.Println(s)
}
这是我得到的结果:
7
[8 2 8 20 4 0]
[8 2 8 20]
[8 4 0 20]
26 32 58
[8 2 8 8 4 0]
我在创建一个切片时得到了一个底层需要数字的底层数组,并将一个切片传递给一个函数并修改一个修改底层数组的元素但是我不确定goroutine是什么样的顺序去。
它打印出第一个S [0]为7,即使它之前的函数调用应该将它修改为8,所以我假设goroutines还没有运行呢? 然后在第二个goroutine函数调用之后打印整个数组,并在第一个函数调用期间进行所有修改(将第一个项修改为8并附加20)。 然而,接下来的2行打印输出是函数内部切片段的打印输出,从逻辑上讲,我刚才说我看到函数调用完成的修改意味着它们应该在该行之前打印出来。
然后我不确定它是如何进行计算或最终打印输出的。
熟悉Go操作方式的人是否可以解释此代码的逻辑进展是什么?
答案 0 :(得分:1)
它打印出第一个S [0]为7,即使它之前的函数调用应该将其修改为8,所以我假设goroutines还没有运行?
是的,这种解释是正确的。 (但不应该制作,请参阅下一节)。
然后在第二个goroutine函数调用之后打印整个数组,并在第一个函数调用期间进行所有修改(将第一个项目修改为8并附加20)。
NO。从来没有想过这些思路。 此时您的应用程序的状态是不定义良好。 1)您启动了修改状态的goroutine,并且您在三个goroutine(您手动启动的主要和两个)之间绝对 没有同步。 2)你的代码是活泼的,这意味着它是a)错误的,活泼的程序从不正确,b)导致未定义的行为。
然而接下来的2行打印输出是函数内部切片段的打印输出,从逻辑上讲,我刚才说我看到函数调用完成的修改意味着它们应该在该行之前打印出来
或多或少,如果你的节目不活泼(见上文)。
您的代码在哪里:main.s
是一个带有支持数组的切片,当您使用go sum(...)
启动goroutines时,它会将其重复两次。两个子对象共享相同的后备阵列。现在在goroutines中写入(s[0]=8
)并将(append(s, 20)
)追加到此子片段。从两个goroutines没有同步。第一个goroutine将使用第二个goroutine同时使用的足够大的后备阵列来实现此追加。
这导致并发写入而没有同步到main.s
的第四个元素,这个元素是生动的,因此是未定义的。
通过从通道(<-c, <-c
)读取两次,你引入了两个手动启动的goroutine和主goroutine之间的同步,你的程序返回到串行执行(但它的状态仍然没有很好地定义,因为它是racy)。
用于跟踪正在发生的事情的Println语句也存在问题:这些语句打印的事物处于一个定义不明确的状态,因为它是从不同的goroutine同时访问的;两者都可以打印任意数据。
总结:你的程序是活泼的,它的输出是未定义的,没有“理由”或“解释”为什么它打印它打印的东西,它纯粹的机会。 总是在竞争检测器(-race)下尝试你的代码,不要试图为racy代码做出伪解释。
如何修复:
分而治之的方法很好,也没关系。什么是危险的,因此很容易出错(因为它发生在你身上)正在修改goroutine内的子片。虽然s[0]=9
]在您的情况下是无害的,但附加会严重干扰共享后备阵列。
不要修改goroutine中的s。如果您必须:确保您有一个新的后备阵列。
(不要试图混合切片和并发。两者都有一些微妙之处,切片共享后备数组和并发性未定义的racy行为的问题。特别是如果你是这个的新手。学习一个,然后另一个,然后结合两者。)