Golang写时复制是否是原子性的?
Posted 衣舞晨风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang写时复制是否是原子性的?相关的知识,希望对你有一定的参考价值。
建议先阅读下Go汇编语言的入门教程
https://go.dev/doc/asm
先说一下我这边的一个简化场景吧,有一个定时任务定时从数据库获取数据,也就是对应实例代码中的getNewProject(),获取完数据后,会直接赋给变量projectMap(projectMap其实就是作为一个缓存来用的);还会有程序从projectMap获取对应的信息。
这个场景其实就是一个简单的写时复制。对于获取在赋值过程中,获取到旧值,也是允许的。
有个疑问点就是在赋值的这个操作是不是原子的呢?
比如示例代码中的第8行。
验证代码:
package main
var (
projectMap = make(map[string]*Project)
)
func main()
projectMap = getNewProject()
func getNewProject() map[string]*Project
items := make(map[string]*Project)
item := new(Project)
item.ID = "project_id"
item.Name = "project_name"
items[item.ID] = item
return items
type Project struct
ID string `json:"id"`
Name string `json:"name"`
在网上也找到一些资料,说是原子性的:
- 看看开源项目都是如何做的
- https://github.com/fagongzi/manba/blob/master/pkg/proxy/dispatcher_meta.go#L174
- 看看一些对于Copy-On-Write的讨论
- https://chunlife.top/2019/09/03/copy-on-write%E6%8A%80%E6%9C%AF/
- https://github.com/Terry-Mao/gopush-cluster/issues/44
但终究还是自己探索一下的比较好对吧,哈哈。
要想看这个赋值操作是不是原子性的,那咱们看下汇编代码吧。
查看汇编,咱们分为两步:
- 编译golang代码为.o文件
- 反编译.o文件
PS D:\\Code\\Golang\\github\\jiankunking\\cow-test> go tool compile -N -l main\\main.go
PS D:\\Code\\Golang\\github\\jiankunking\\cow-test> go tool objdump .\\main.o
TEXT "".main(SB) gofile..D:/Code/Golang/github/jiankunking/cow-test/main/main.go
main.go:7 0x22f1 493b6610 CMPQ 0x10(R14), SP
main.go:7 0x22f5 763e JBE 0x2335
main.go:7 0x22f7 4883ec08 SUBQ $0x8, SP
main.go:7 0x22fb 48892c24 MOVQ BP, 0(SP)
main.go:7 0x22ff 488d2c24 LEAQ 0(SP), BP
main.go:8 0x2303 e800000000 CALL 0x2308 [1:5]R_CALL:"".getNewProject
main.go:8 0x2308 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:8 0x230f 6690 NOPW
main.go:8 0x2311 7402 JE 0x2315
main.go:8 0x2313 eb09 JMP 0x231e
main.go:8 0x2315 48890500000000 MOVQ AX, 0(IP) [3:7]R_PCREL:"".projectMap
main.go:8 0x231c eb0e JMP 0x232c
main.go:8 0x231e 488d3d00000000 LEAQ 0(IP), DI [3:7]R_PCREL:"".projectMap
main.go:8 0x2325 e800000000 CALL 0x232a [1:5]R_CALL:runtime.gcWriteBarrier<1>
main.go:8 0x232a eb00 JMP 0x232c
main.go:9 0x232c 488b2c24 MOVQ 0(SP), BP
main.go:9 0x2330 4883c408 ADDQ $0x8, SP
main.go:9 0x2334 c3 RET
main.go:7 0x2335 e800000000 CALL 0x233a [1:5]R_CALL:runtime.morestack_noctxt
main.go:7 0x233a ebb5 JMP "".main(SB)
TEXT "".getNewProject(SB) gofile..D:/Code/Golang/github/jiankunking/cow-test/main/main.go
main.go:11 0x233c 493b6610 CMPQ 0x10(R14), SP
main.go:11 0x2340 0f8600010000 JBE 0x2446
main.go:11 0x2346 4883ec58 SUBQ $0x58, SP
main.go:11 0x234a 48896c2450 MOVQ BP, 0x50(SP)
main.go:11 0x234f 488d6c2450 LEAQ 0x50(SP), BP
main.go:11 0x2354 48c744242000000000 MOVQ $0x0, 0x20(SP)
main.go:12 0x235d e800000000 CALL 0x2362 [1:5]R_CALL:runtime.makemap_small<1>
main.go:12 0x2362 4889442428 MOVQ AX, 0x28(SP)
main.go:14 0x2367 488d0500000000 LEAQ 0(IP), AX [3:7]R_PCREL:type."".Project
main.go:14 0x236e e800000000 CALL 0x2373 [1:5]R_CALL:runtime.newobject<1>
main.go:14 0x2373 4889442430 MOVQ AX, 0x30(SP)
main.go:15 0x2378 48c740080a000000 MOVQ $0xa, 0x8(AX)
main.go:15 0x2380 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:15 0x2387 7402 JE 0x238b
main.go:15 0x2389 eb0c JMP 0x2397
main.go:15 0x238b 488d1500000000 LEAQ 0(IP), DX [3:7]R_PCREL:go.string."project_id"
main.go:15 0x2392 488910 MOVQ DX, 0(AX)
main.go:15 0x2395 eb11 JMP 0x23a8
main.go:15 0x2397 4889c7 MOVQ AX, DI
main.go:15 0x239a 488d1500000000 LEAQ 0(IP), DX [3:7]R_PCREL:go.string."project_id"
main.go:15 0x23a1 e800000000 CALL 0x23a6 [1:5]R_CALL:runtime.gcWriteBarrierDX
main.go:15 0x23a6 eb00 JMP 0x23a8
main.go:16 0x23a8 488b542430 MOVQ 0x30(SP), DX
main.go:16 0x23ad 8402 TESTB AL, 0(DX)
main.go:16 0x23af 48c742180c000000 MOVQ $0xc, 0x18(DX)
main.go:16 0x23b7 488d7a10 LEAQ 0x10(DX), DI
main.go:16 0x23bb 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:16 0x23c2 7402 JE 0x23c6
main.go:16 0x23c4 eb0d JMP 0x23d3
main.go:16 0x23c6 488d3500000000 LEAQ 0(IP), SI [3:7]R_PCREL:go.string."project_name"
main.go:16 0x23cd 48897210 MOVQ SI, 0x10(DX)
main.go:16 0x23d1 eb10 JMP 0x23e3
main.go:16 0x23d3 488d1500000000 LEAQ 0(IP), DX [3:7]R_PCREL:go.string."project_name"
main.go:16 0x23da 6690 NOPW
main.go:16 0x23dc e800000000 CALL 0x23e1 [1:5]R_CALL:runtime.gcWriteBarrierDX
main.go:16 0x23e1 eb00 JMP 0x23e3
main.go:18 0x23e3 488b542430 MOVQ 0x30(SP), DX
main.go:18 0x23e8 8402 TESTB AL, 0(DX)
main.go:18 0x23ea 488b0a MOVQ 0(DX), CX
main.go:18 0x23ed 488b7a08 MOVQ 0x8(DX), DI
main.go:18 0x23f1 48894c2440 MOVQ CX, 0x40(SP)
main.go:18 0x23f6 48897c2448 MOVQ DI, 0x48(SP)
main.go:18 0x23fb 488b5c2428 MOVQ 0x28(SP), BX
main.go:18 0x2400 488d0500000000 LEAQ 0(IP), AX [3:7]R_PCREL:type.map[string]*"".Project
main.go:18 0x2407 e800000000 CALL 0x240c [1:5]R_CALL:runtime.mapassign_faststr<1>
main.go:18 0x240c 4889442438 MOVQ AX, 0x38(SP)
main.go:18 0x2411 8400 TESTB AL, 0(AX)
main.go:18 0x2413 488b542430 MOVQ 0x30(SP), DX
main.go:18 0x2418 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:18 0x241f 7402 JE 0x2423
main.go:18 0x2421 eb05 JMP 0x2428
main.go:18 0x2423 488910 MOVQ DX, 0(AX)
main.go:18 0x2426 eb0a JMP 0x2432
main.go:18 0x2428 4889c7 MOVQ AX, DI
main.go:18 0x242b e800000000 CALL 0x2430 [1:5]R_CALL:runtime.gcWriteBarrierDX
main.go:18 0x2430 eb00 JMP 0x2432
main.go:19 0x2432 488b442428 MOVQ 0x28(SP), AX
main.go:19 0x2437 4889442420 MOVQ AX, 0x20(SP)
main.go:19 0x243c 488b6c2450 MOVQ 0x50(SP), BP
main.go:19 0x2441 4883c458 ADDQ $0x58, SP
main.go:19 0x2445 c3 RET
main.go:11 0x2446 e800000000 CALL 0x244b [1:5]R_CALL:runtime.morestack_noctxt
main.go:11 0x244b e9ecfeffff JMP "".getNewProject(SB)
TEXT "".init(SB) gofile..D:/Code/Golang/github/jiankunking/cow-test/main/main.go
main.go:4 0x2450 493b6610 CMPQ 0x10(R14), SP
main.go:4 0x2454 763e JBE 0x2494
main.go:4 0x2456 4883ec08 SUBQ $0x8, SP
main.go:4 0x245a 48892c24 MOVQ BP, 0(SP)
main.go:4 0x245e 488d2c24 LEAQ 0(SP), BP
main.go:4 0x2462 e800000000 CALL 0x2467 [1:5]R_CALL:runtime.makemap_small<1>
main.go:4 0x2467 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:4 0x246e 6690 NOPW
main.go:4 0x2470 7402 JE 0x2474
main.go:4 0x2472 eb09 JMP 0x247d
main.go:4 0x2474 48890500000000 MOVQ AX, 0(IP) [3:7]R_PCREL:"".projectMap
main.go:4 0x247b eb0e JMP 0x248b
main.go:4 0x247d 488d3d00000000 LEAQ 0(IP), DI [3:7]R_PCREL:"".projectMap
main.go:4 0x2484 e800000000 CALL 0x2489 [1:5]R_CALL:runtime.gcWriteBarrier<1>
main.go:4 0x2489 eb00 JMP 0x248b
main.go:4 0x248b 488b2c24 MOVQ 0(SP), BP
main.go:4 0x248f 4883c408 ADDQ $0x8, SP
main.go:4 0x2493 c3 RET
main.go:4 0x2494 e800000000 CALL 0x2499 [1:5]R_CALL:runtime.morestack_noctxt
main.go:4 0x2499 ebb5 JMP "".init(SB)
TEXT type..eq."".Project(SB) gofile..<autogenerated>
gofile..<autogenerated>:1 0x2713 493b6610 CMPQ 0x10(R14), SP
gofile..<autogenerated>:1 0x2717 0f8610010000 JBE 0x282d
gofile..<autogenerated>:1 0x271d 4883ec58 SUBQ $0x58, SP
gofile..<autogenerated>:1 0x2721 48896c2450 MOVQ BP, 0x50(SP)
gofile..<autogenerated>:1 0x2726 488d6c2450 LEAQ 0x50(SP), BP
gofile..<autogenerated>:1 0x272b 4889442460 MOVQ AX, 0x60(SP)
gofile..<autogenerated>:1 0x2730 48895c2468 MOVQ BX, 0x68(SP)
gofile..<autogenerated>:1 0x2735 c644241e00 MOVB $0x0, 0x1e(SP)
gofile..<autogenerated>:1 0x273a 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x273f 488b5208 MOVQ 0x8(DX), DX
gofile..<autogenerated>:1 0x2743 4889542428 MOVQ DX, 0x28(SP)
gofile..<autogenerated>:1 0x2748 488b542468 MOVQ 0x68(SP), DX
gofile..<autogenerated>:1 0x274d 488b5208 MOVQ 0x8(DX), DX
gofile..<autogenerated>:1 0x2751 4889542420 MOVQ DX, 0x20(SP)
gofile..<autogenerated>:1 0x2756 4839542428 CMPQ DX, 0x28(SP)
gofile..<autogenerated>:1 0x275b 7405 JE 0x2762
gofile..<autogenerated>:1 0x275d e9b3000000 JMP 0x2815
gofile..<autogenerated>:1 0x2762 eb00 JMP 0x2764
gofile..<autogenerated>:1 0x2764 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x2769 488b5218 MOVQ 0x18(DX), DX
gofile..<autogenerated>:1 0x276d 4889542420 MOVQ DX, 0x20(SP)
gofile..<autogenerated>:1 0x2772 488b542468 MOVQ 0x68(SP), DX
gofile..<autogenerated>:1 0x2777 488b5218 MOVQ 0x18(DX), DX
gofile..<autogenerated>:1 0x277b 4889542428 MOVQ DX, 0x28(SP)
gofile..<autogenerated>:1 0x2780 4839542420 CMPQ DX, 0x20(SP)
gofile..<autogenerated>:1 0x2785 7405 JE 0x278c
gofile..<autogenerated>:1 0x2787 e987000000 JMP 0x2813
gofile..<autogenerated>:1 0x278c eb00 JMP 0x278e
gofile..<autogenerated>:1 0x278e 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x2793 488b5208 MOVQ 0x8(DX), DX
gofile..<autogenerated>:1 0x2797 4889542428 MOVQ DX, 0x28(SP)
gofile..<autogenerated>:1 0x279c 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x27a1 488b12 MOVQ 0(DX), DX
gofile..<autogenerated>:1 0x27a4 4889542448 MOVQ DX, 0x48(SP)
gofile..<autogenerated>:1 0x27a9 488b542468 MOVQ 0x68(SP), DX
gofile..<autogenerated>:1 0x27ae 488b1a MOVQ 0(DX), BX
gofile..<autogenerated>:1 0x27b1 48895c2440 MOVQ BX, 0x40(SP)
gofile..<autogenerated>:1 0x27b6 488b4c2428 MOVQ 0x28(SP), CX
gofile..<autogenerated>:1 0x27bb 488b442448 MOVQ 0x48(SP), AX
gofile..<autogenerated>:1 0x27c0 e800000000 CALL 0x27c5 [1:5]R_CALL:runtime.memequal<1>
gofile..<autogenerated>:1 0x27c5 8844241f MOVB AL, 0x1f(SP)
gofile..<autogenerated>:1 0x27c9 84c0 TESTL AL, AL
gofile..<autogenerated>:1 0x27cb 7502 JNE 0x27cf
gofile..<autogenerated>:1 0x27cd eb41 JMP 0x2810
gofile..<autogenerated>:1 0x27cf eb00 JMP 0x27d1
gofile..<autogenerated>:1 0x27d1 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x27d6 488b5218 MOVQ 0x18(DX), DX
gofile..<autogenerated>:1 0x27da 4889542428 MOVQ DX, 0x28(SP)
gofile..<autogenerated>:1 0x27df 488b542460 MOVQ 0x60(SP), DX
gofile..<autogenerated>:1 0x27e4 488b5210 MOVQ 0x10(DX), DX
gofile..<autogenerated>:1 0x27e8 4889542438 MOVQ DX, 0x38(SP)
gofile..<autogenerated>:1 0x27ed 488b542468 MOVQ 0x68(SP), DX
gofile..<autogenerated>:1 0x27f2 488b5a10 MOVQ 0x10(DX), BX
gofile..<autogenerated>:1 0x27f6 48895c2430 MOVQ BX, 0x30(SP)
gofile..<autogenerated>:1 0x27fb 488b4c2428 MOVQ 0x28(SP), CX
gofile..<autogenerated>:1 0x2800 488b442438 MOVQ 0x38(SP), AX
gofile..<autogenerated>:1 0x2805 e800000000 CALL 0x280a [1:5]R_CALL:runtime.memequal<1>
gofile..<autogenerated>:1 0x280a 8844241e MOVB AL, 0x1e(SP)
gofile..<autogenerated>:1 0x280e eb0e JMP 0x281e
gofile..<autogenerated>:1 0x2810 eb05 JMP 0x2817
gofile..<autogenerated>:1 0x2812 90 NOPL
gofile..<autogenerated>:1 0x2813 eb02 JMP 0x2817
gofile..<autogenerated>:1 0x2815 eb00 JMP 0x2817
gofile..<autogenerated>:1 0x2817 c644241e00 MOVB $0x0, 0x1e(SP)
gofile..<autogenerated>:1 0x281c eb00 JMP 0x281e
gofile..<autogenerated>:1 0x281e 0fb644241e MOVZX 0x1e(SP), AX
gofile..<autogenerated>:1 0x2823 488b6c2450 MOVQ 0x50(SP), BP
gofile..<autogenerated>:1 0x2828 4883c458 ADDQ $0x58, SP
gofile..<autogenerated>:1 0x282c c3 RET
gofile..<autogenerated>:1 0x282d 4889442408 MOVQ AX, 0x8(SP)
gofile..<autogenerated>:1 0x2832 48895c2410 MOVQ BX, 0x10(SP)
gofile..<autogenerated>:1 0x2837 e800000000 CALL 0x283c [1:5]R_CALL:runtime.morestack_noctxt
gofile..<autogenerated>:1 0x283c 488b442408 MOVQ 0x8(SP), AX
gofile..<autogenerated>:1 0x2841 488b5c2410 MOVQ 0x10(SP), BX
gofile..<autogenerated>:1 0x2846 e9c8feffff JMP type..eq."".Project(SB)
PS D:\\Code\\Golang\\github\\jiankunking\\cow-test>
先从汇编代码中摘取出,第8行代码对应的汇编
main.go:8 0x2303 e800000000 CALL 0x2308 [1:5]R_CALL:"".getNewProject
main.go:8 0x2308 833d0000000000 CMPL $0x0, 0(IP) [2:6]R_PCREL:runtime.writeBarrier+-1
main.go:8 0x230f 6690 NOPW
main.go:8 0x2311 7402 JE 0x2315
main.go:8 0x2313 eb09 JMP 0x231e
main.go:8 0x2315 48890500000000 MOVQ AX, 0(IP) [3:7]R_PCREL:"".projectMap
main.go:8 0x231c eb0e JMP 0x232c
main.go:8 0x231e 488d3d00000000 LEAQ 0(IP), DI [3:7]R_PCREL:"".projectMap
main.go:8 0x2325 e800000000 CALL 0x232a [1:5]R_CALL:runtime.gcWriteBarrier<1>
main.go:8 0x232a eb00 JMP 0x232c
从汇编代码中可以看出,赋值的核心在下面这几句:
// 将AX寄存器中的值赋给0(IP)
main.go:8 0x2315 48890500000000 MOVQ AX, 0(IP) [3:7]R_PCREL:"".projectMap
// 跳转
main.go:8 0x231c eb0e JMP 0x232c
// 将0(IP)中值的地址赋给DI
main.go:8 0x231e 488d3d00000000 LEAQ 0(IP), DI [3:7]R_PCREL:"".projectMap
从汇编可以看到赋值是只有一步,但获取值及赋值是分开的,也就是说对于写时复制这种场景来说,直接赋值是没有问题的
基础知识补充:
MOVQ
-
movb(8位)、movw(16位)、movl(32位)、movq(64位)
-
寄存器寻址:
movl %eax, %edx
eax -> edx
https://blog.csdn.net/luoyhang003/article/details/46786591/
TESTB
TEST指令的行为与AND指令一样,除了不改变目的寄存器的值。例如,testq %rax, %rax 用来检查 %rax 是负数、零、还是正数。
LEAQ vs MOVQ
https://stackoverflow.com/questions/1699748/what-is-the-difference-between-mov-and-lea
PCDATA
https://www.cnblogs.com/binHome/p/13034103.html
CMPL
CMPL %eax,%ebx
==>
[ebx]-[eax],就是把第二个数减去第一个数
JMP
无条件转移指令
JMP 389无条件转至0x0185地址处(十进制389转换成十六进制0x0185)
CALL
调用函数
CALL runtime.printnl(SB)表示通过printnl函数的内存地址发起调用
伪计数器
FP: Frame pointer: arguments and locals.(指向当前栈帧)
PC: Program counter: jumps and branches.(指向指令地址)
SB: Static base pointer: global symbols.(指向全局符号表)
SP: Stack pointer: top of stack.(指向当前栈顶部)
注意: 栈是向下整长 golang的汇编是调用者维护参数返回值跟返回地址。所以FP的值小于参数跟返回值
本机环境
x86-64( 又称x64,即英文词64-bit extended,64位拓展 的简写)是x86架构的64位拓展,向后兼容于16位及32位的x86架构。x64于1999年由AMD设计,AMD首次公开64位集以扩展给x86,称为“AMD64”。
编译命令详解
PS D:\\Code\\Golang\\github\\jiankunking\\cow-test> go tool compile -help
usage: compile [options] file.go...
-% int
debug non-static initializers
-+ compiling runtime
-B disable bounds checking
-C disable printing of columns in error messages
-D path
set relative path for local imports
-E debug symbol export
-G accept generic code (default 3)
-I directory
add directory to import search path
-K debug missing line numbers
-L show full file names in error messages
-N disable optimizations
-S print assembly listing
-V print version and exit
-W debug parse tree after type checking
-asan
build code compatible with C/C++ address sanitizer
-asmhdr file
write assembly header to file
-bench file
append benchmark times to file
-blockprofile file
write block profile to file
-buildid id
record id as the build id in the export metadata
-c int
concurrency during compilation (1 means no concurrency) (default 1)
-clobberdead
clobber dead stack slots (for debugging)
-clobberdeadreg
clobber dead registers (for debugging)
-complete
compiling complete package (no C or assembly)
-cpuprofile file
write cpu profile to file
-d value
enable debugging settings; try -d help
-dwarf
generate DWARF symbols (default true)
-dwarfbasentries
use base address selection entries in DWARF (default true)
-dwarflocationlists
add location lists to DWARF in optimized mode (default true)
-dynlink
support references to Go symbols defined in other shared libraries
-e no limit on number of errors reported
-embedcfg file
read go:embed configuration from file
-gendwarfinl int
generate DWARF inline info records (default 2)
-goversion string
required version of the runtime
-h halt on error
-importcfg file
read import configuration from file
-importmap definition
add definition of the form source=actual to import map
-installsuffix suffix
set pkg directory suffix
-j debug runtime-initialized variables
-json string
version,file for JSON compiler/optimizer detail output
-l disable inlining
-lang string
Go language version source code expects
-linkobj file
write linker-specific object to file
-linkshared
generate code that will be linked against Go shared libraries
-live
debug liveness analysis
-m print optimization decisions
-memprofile file
write memory profile to file
-memprofilerate rate
set runtime.MemProfileRate to rate
-msan
build code compatible with C/C++ memory sanitizer
-mutexprofile file
write mutex profile to file
-nolocalimports
reject local (relative) imports
-o file
write output to file
-p path
set expected package import path
-pack
write to file.a instead of file.o
-r debug generated wrappers
-race
enable race detector
-shared
generate code that can be linked into a shared library
-smallframes
reduce the size limit for stack allocated objects
-spectre list
enable spectre mitigations in list (all, index, ret)
-std
compiling standard library
-symabis file
read symbol ABIs from file
-t enable tracing for debugging the compiler
-traceprofile file
write an execution trace to file
-trimpath prefix
remove prefix from recorded source file paths
-v increase debug verbosity
-w debug type checking
-wb
enable write barrier (default true)
拓展阅读
https://stackoverflow.com/questions/70332316/whats-the-differenct-between-go-tool-compile-and-go-tool-objdump
https://xargin.com/plan9-assembly/
https://github.com/cch123/golang-notes/blob/master/assembly.md
https://go.dev/doc/asm
以上是关于Golang写时复制是否是原子性的?的主要内容,如果未能解决你的问题,请参考以下文章