分享第 13 章部分读书笔记,讲的特别条理,细致。
Chapter 13 Processes are Friendly
Being CoW Friendly
在 fork 那个章节,我们说,fork(2) 创建一个新的进程,会复制主进程的一切。物理复制所有数据是一笔很大的开销,所以,现代 Unix 系统采用了一个叫 Cow(copy-on-write 写时复制技术)的东西来处理。顾名思义,Cow 只在需要写入的时候才开始复制数据。所以一个主进程和一个子进程实际上是共享内存里相同的物理数据,直到有一个进程需要修改某些数据,这个时候才复制。
arr = [1,2,3] fork do # At this point the child process has been initialized. # 这时候 child process 已经被初始化了 # Using CoW this process doesn't need to copy the arr variable, # 使用 CoW 这个进程不需要被复制 arr 变量 # since it hasn't modified any shared values it can continue reading # 因为它还没有被修改任何的共享值。 # from the same memory location as the parent process. # 它还可以从主进程的相同内存中读数据 p arr end
arr = [1,2,3] fork do # At this point the child process has been initialized. # Because of CoW the arr variable hasn't been copied yet. # 因为 CoW 还没有复制 arr 这个变量 arr << 4 # The above line of code modifies the array, so a copy of # the array will need to be made for this process before # it can modify it. The array in the parent process remains # unchanged. puts arr # 上面的代码已经修改了那个数组,所以这个子进程会在修改之前 copy 一个数组的副本。 # 而在主进程中的数组依然不会被改变。 end puts arr
输出
[1, 2, 3, 4] => 1188
[1,2,3]
可以看出,arr 并未被改变。
CoW 是牛逼的,不幸的是它不支持 MRI 或 Rubinius.
为了让 CoW 正常的工作,需要按照 CoW 的方式管理内存,可惜 MRI 和 Rubinius 并不是这样做的。
为什么不呢?
MRI 的垃圾回收器(GC)使用了标记和扫描算法。这意味着,GC 调用的时候,必须遍历所有已知的对象和写入,看它们是否应该被收集。这就导致了,每次 GC 运行的时候,内存中的每个对象都会被写入。因此,在 fork 之后,GC 运行一次,就打乱了 CoW 的机制。
这也是 REE 被创建的主要原因之一。可以去 google 一下 REE 是如何解决这个问题的。
(译注:不幸的是,Ruby1.9 仍然不是 Cow Friendly 的,不过有传言说 Ruby2.0 会是 CoW Friendly 的,期望如此)