golang的各种原生类型之间赋值是原子的吗

原始代码

我在项目里写了这样一段代码

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

写一个测试文件 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

写一个测试文件 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

写一个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

写一个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里面还有一种原生数据类型是数组,会不会有人问数组赋值是不是原子的啊? 我猜应该不是吧,这个我还真没研究过,好难猜啊

你可能感兴趣的:(Linux应用编程,golang,开发语言,后端)