我在项目里写了这样一段代码
package id2name
import (
"time"
)
type Id2Name struct {
m map[int]string
}
func New() (*Id2Name, error) {
m, err := getId2NameMap()
if err != nil {
return nil, err
}
ins := &Id2Name{
m: m,
}
go ins.reload()
return ins, nil
}
func (this *Id2Name) GetNameById(id int) (string, bool) {
name, ok := this.m[id]
return name, ok
}
func (this *Id2Name) reload() {
t := time.NewTicker(time.Hour)
for {
<-t.C
m, err := getId2NameMap()
if err != nil {
log.ErrAndAlert("reload err")
continue
}
this.m = m
log.Info("reload ok")
}
}
这段代码中,第38行新的map覆盖旧的map时,我并没有使用锁,这样做会不会导致map赋值的前后,另一个读者上下文读到不完整的数据呢?
我心里觉得不会,当然如果我能证明map赋值是原子的,就可以嘴上大胆的说yes了。
写一个测试文件 map.go
package main
func main() {
a := map[string]int{"one": 1, "two": 2}
b := a
_ = b
}
然后编译并反汇编
go build -gcflags="-N -l" -o map map.go
go tool objdump -gnu -S map > map.asm
查看map.asm,找到main.main函数
TEXT main.main(SB) /data/home/emperzhao/output_dir/go-learn/map.go
func main() {
0x4589a0 4c8da42410ffffff LEAQ 0xffffff10(SP), R12 // lea 0xffffff10(%rsp),%r12
0x4589a8 4d3b6610 CMPQ 0x10(R14), R12 // cmp 0x10(%r14),%r12
0x4589ac 0f8627010000 JBE 0x458ad9 // jbe 0x458ad9
0x4589b2 4881ec70010000 SUBQ $0x170, SP // sub $0x170,%rsp
0x4589b9 4889ac2468010000 MOVQ BP, 0x168(SP) // mov %rbp,0x168(%rsp)
0x4589c1 488dac2468010000 LEAQ 0x168(SP), BP // lea 0x168(%rsp),%rbp
a := map[string]int{"one": 1, "two": 2}
0x4589c9 440f117c2468 MOVUPS X15, 0x68(SP) // movups %xmm15,0x68(%rsp)
...... 中间省略
0x458ab7 488b4c2420 MOVQ 0x20(SP), CX // mov 0x20(%rsp),%rcx
0x458abc 488908 MOVQ CX, 0(AX) // mov %rcx,(%rax)
b := a
0x458abf 488b442430 MOVQ 0x30(SP), AX // mov 0x30(%rsp),%rax
0x458ac4 4889442428 MOVQ AX, 0x28(SP) // mov %rax,0x28(%rsp)
}
注意看最后2行,a赋值给b的时候,虽然看上去是2条汇编指令,但是只写了一次内存,前一条指令是把指针读出来缓存到AX寄存器里,后一条才是赋值。
所以说,golang的map本质上就是一个指针,一条mov指令就赋值了,是原子的。
写一个测试文件 slice.go
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
s2 := s
fmt.Println(s2)
}
同上,编译并反汇编,看一下main.mian的代码
TEXT main.main(SB) /data/home/emperzhao/output_dir/go-learn/slice.go
func main() {
0x480540 4c8d6424f0 LEAQ -0x10(SP), R12 // lea -0x10(%rsp),%r12
0x480545 4d3b6610 CMPQ 0x10(R14), R12 // cmp 0x10(%r14),%r12
0x480549 0f863d010000 JBE 0x48068c // jbe 0x48068c
0x48054f 4881ec90000000 SUBQ $0x90, SP // sub $0x90,%rsp
0x480556 4889ac2488000000 MOVQ BP, 0x88(SP) // mov %rbp,0x88(%rsp)
0x48055e 488dac2488000000 LEAQ 0x88(SP), BP // lea 0x88(%rsp),%rbp
s := []int{1, 2, 3, 4, 5}
0x480566 488d05d3860000 LEAQ 0x86d3(IP), AX // lea 0x86d3(%rip),%rax
...... 省略
0x4805c3 48894c2458 MOVQ CX, 0x58(SP) // mov %rcx,0x58(%rsp)
0x4805c8 48c744246005000000 MOVQ $0x5, 0x60(SP) // movq $0x5,0x60(%rsp)
0x4805d1 48c744246805000000 MOVQ $0x5, 0x68(SP) // movq $0x5,0x68(%rsp)
s2 := s
0x4805da 48894c2440 MOVQ CX, 0x40(SP) // mov %rcx,0x40(%rsp)
0x4805df 48c744244805000000 MOVQ $0x5, 0x48(SP) // movq $0x5,0x48(%rsp)
0x4805e8 48c744245005000000 MOVQ $0x5, 0x50(SP) // movq $0x5,0x50(%rsp)
fmt.Println(s2)
0x4805f1 440f117c2430 MOVUPS X15, 0x30(SP) // movups %xmm15,0x30(%rsp)
0x4805f7 488d542430 LEAQ 0x30(SP), DX // lea 0x30(%rsp),%rdx
0x4805fc 4889542420 MOVQ DX, 0x20(SP) // mov %rdx,0x20(%rsp)
0x480601 488b442440 MOVQ 0x40(SP), AX // mov 0x40(%rsp),%rax
......省略
可以看到s赋值给s2的时候,写了3次内存,如果赋值的时候另一个上下文有人读s2,会有风险读到不完整的数据
写一个interface.go测试代码
package main
import "fmt"
type MyInterface interface{}
func main() {
var iface MyInterface
iface = 42
if2 := iface
fmt.Println(if2)
}
看反汇编
TEXT main.main(SB) /data/home/emperzhao/output_dir/go-learn/interface.go
func main() {
0x480540 493b6610 CMPQ 0x10(R14), SP // cmp 0x10(%r14),%rsp
0x480544 0f868e000000 JBE 0x4805d8 // jbe 0x4805d8
0x48054a 4883ec70 SUBQ $0x70, SP // sub $0x70,%rsp
0x48054e 48896c2468 MOVQ BP, 0x68(SP) // mov %rbp,0x68(%rsp)
0x480553 488d6c2468 LEAQ 0x68(SP), BP // lea 0x68(%rsp),%rbp
var iface MyInterface
0x480558 440f117c2420 MOVUPS X15, 0x20(SP) // movups %xmm15,0x20(%rsp)
iface = 42
0x48055e 488d157b780000 LEAQ 0x787b(IP), DX // lea 0x787b(%rip),%rdx
0x480565 4889542420 MOVQ DX, 0x20(SP) // mov %rdx,0x20(%rsp)
0x48056a 488d3587570300 LEAQ 0x35787(IP), SI // lea 0x35787(%rip),%rsi
0x480571 4889742428 MOVQ SI, 0x28(SP) // mov %rsi,0x28(%rsp)
if2 := iface
0x480576 4889542430 MOVQ DX, 0x30(SP) // mov %rdx,0x30(%rsp)
0x48057b 4889742438 MOVQ SI, 0x38(SP) // mov %rsi,0x38(%rsp)
fmt.Println(if2)
0x480580 440f117c2440 MOVUPS X15, 0x40(SP) // movups %xmm15,0x40(%rsp)
0x480586 488d442440 LEAQ 0x40(SP), AX // lea 0x40(%rsp),%rax
0x48058b 4889442418 MOVQ AX, 0x18(SP) // mov %rax,0x18(%rsp)
......省略
可以看到interface赋值写了2次内存,说明golang的interface并不是一个指针,而是一个包含2个字段的结构体
写一个string.go测试程序
package main
import "fmt"
func main() {
str := "Hello, World!"
s2 := str
fmt.Println(s2)
}
看反汇编
TEXT main.main(SB) /data/home/emperzhao/output_dir/go-learn/string.go
func main() {
0x480540 493b6610 CMPQ 0x10(R14), SP // cmp 0x10(%r14),%rsp
0x480544 0f86bc000000 JBE 0x480606 // jbe 0x480606
0x48054a 4883ec78 SUBQ $0x78, SP // sub $0x78,%rsp
0x48054e 48896c2470 MOVQ BP, 0x70(SP) // mov %rbp,0x70(%rsp)
0x480553 488d6c2470 LEAQ 0x70(SP), BP // lea 0x70(%rsp),%rbp
str := "Hello, World!"
0x480558 488d0d9e940100 LEAQ 0x1949e(IP), CX // lea 0x1949e(%rip),%rcx
0x48055f 48894c2428 MOVQ CX, 0x28(SP) // mov %rcx,0x28(%rsp)
0x480564 48c74424300d000000 MOVQ $0xd, 0x30(SP) // movq $0xd,0x30(%rsp)
s2 := str
0x48056d 48894c2438 MOVQ CX, 0x38(SP) // mov %rcx,0x38(%rsp)
0x480572 48c74424400d000000 MOVQ $0xd, 0x40(SP) // movq $0xd,0x40(%rsp)
fmt.Println(s2)
0x48057b 440f117c2448 MOVUPS X15, 0x48(SP) // movups %xmm15,0x48(%rsp)
0x480581 488d4c2448 LEAQ 0x48(SP), CX // lea 0x48(%rsp),%rcx
0x480586 48894c2420 MOVQ CX, 0x20(SP) // mov %rcx,0x20(%rsp)
0x48058b 488b442438 MOVQ 0x38(SP), AX // mov 0x38(%rsp),%rax
......省略
可以看到string赋值也写了2次内存,说明string也是包含2个字段的结构体
map赋值是原子的,其他的都不是
slice赋值要写3次内存(cap,len,pointer共3个字段)
interface赋值要写2次内存(data-type,pointer共2个字段)
string赋值要写2次内存(data-len,pointer共2个字段)
至于golang里面的 int bool uint64 之类的,毫无疑问是原子的,根本不需要验证
golang里面还有一种原生数据类型是数组,会不会有人问数组赋值是不是原子的啊? 我猜应该不是吧,这个我还真没研究过,好难猜啊