1 经常使用C语言的同学,知道指针在赋值的过程中是一个原子写的过程,因为指针是一个机器字(machine word),这是一个原子写的过程。同样在java的的变量赋值也是类似的,因为在Java中除了八种基本类型,其他的所有类型也是一个header对象的指针
2 在Go语言中变量的赋值是Copy语义的,但是在赋值的过程中可能会有一些小的hack行为,和我们通常理解的指针赋值不一样。例如我们把一个结构体的指针赋值给一个接口。
代码如下:
package main
import "fmt"
func main() {
ben := &Ben{10, "Ben"}
jerry := &Jerry{"Jerry"}
var maker IceCreamMaker
var loop0,loop1 func()
maker = jerry
loop0 = func() {
maker = ben
go loop1()
}
loop1 = func() {
maker = jerry
go loop0()
}
go loop0()
for{
maker.Hello()
}
}
type IceCreamMaker interface {
Hello()
}
type Ben struct {
id int
name string
}
type Jerry struct {
name string
}
func (ben *Ben) Hello() {
fmt.Printf(`Ben said,"Hello my name is %s"`, ben.name)
fmt.Println()
}
func (jerry *Jerry) Hello() {
fmt.Printf(`Jerry said,"Hello my name is %s"`, jerry.name)
fmt.Println()
}
以上代码在运行的过程中可能会出现
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x5 pc=0x8e54d3]
的panic。究其原因就是goroutin在并发的给maker接口变量赋值的过程中不是一个原子的行为。Go接口不是我们在其他语言中理解的一个无结构的成员。
看以看到接口是有两个字段的 ,一部分指向结构一部分指向数据。所以我们在把一个指针赋值给一个接口的过程中go的编译器会通过指针自动分析出结构和数据部分,然后就有了2次的写的行为,在并发过程中就会产生interface变量的数据的破坏,产生runtime的panic. 由于Ben和Jerry这两个结构体不是一样的导致内存布局也不相同。我们假设在并发的过程中maker变量的Type字段指向了Bob结构体但是Data却是指向了Jerry的数据,此时runtime根据Bob的内存布局去在jerry中寻找name就会产生未定义的行为