memory model

https://golang.org/ref/mem

heppens-before的概念

内存重排

为了读写内存的效率,会对读写指令进行重新排列,这是cpu重排。 还有编译器重排:

x = 0
for i in range(100):
    x = 1
    fmt.Println(x)

编译器优化后

x = 1
for i in range(100):
    fmt.Println(x)

但是一旦x被别的goroutine修改,就会无法出现预期的结果。

CPU为了抚平内核、内存和硬盘之间的读写速度差异,使用多级缓存。
因此对于多线程的程序,CPU提供锁机制,即内存屏障,memory barrier,要求所有到cache line中的操作都要立马刷到内存中去,对于多核cpu还有mesi 缓存一致性协议去保障。

copy-on-write

写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。

  • redis如何实现bgsave
    • redis单线程,现在双线程(1 网络 1 工作)
    • fork进程, 地址空间和父亲一样,两个进程指向同一个地址空间,新老不影响,当父子进程中有更改段的行为发生时,再为子进程相应的段分配地址空间,copy-on-write
  • 热门数据会产生全局的热点,全查一个key redis会被打死
    • 缓存到内存中
    • 这个更新怎么办,后台goroutine定期去查询,更新,更新时可以使用加锁,或者使用atomic.Value进行原子替换

reference

https://juejin.cn/post/6844903702373859335

内存管理

参考TCMalloc,Thread Cache Malloc,Go的内存管理借鉴了TCMalloc。

内存碎片:内存的不断申请和释放,内存会存在大量的碎片,降低内存使用率。为了解决内存碎片,可以将几个连续的未使用的内存块合并。

去大锁:每个线程的内存使用有一个大锁。

每个P会挂载一个mcache,goroutine程序申请小块内存时,会从mcache中分配,这样不用加锁。

所有工作线程还共用mcentral,mcache中不够用,就会去mcentral中获取。还有下一级的mhead,mheap中没有会到操作系统中要。

page:内存页,8k内存空间。Go和操作系统之间内存申请和释放,都是page单位。

span:内存块,一个或者连续多个page组成span。