什么是写时复制?


133

我想知道什么是写时复制及其用途?Sun JDK教程中多次提到“写时复制数组”一词,但我不明白它的含义。

Answers:


155

我本来要写我自己的解释,但是这篇Wikipedia文章几乎总结了一下。

这是基本概念:

写时复制(有时称为“ COW”)是计算机编程中使用的一种优化策略。基本思想是,如果多个调用方请求最初无法区分的资源,则可以为他们提供指向同一资源的指针。可以一直保持此功能,直到调用者尝试修改其资源的“副本”为止,此时将创建一个真正的私有副本,以防止其他所有人看到更改。所有这些对于呼叫者都是透明的。主要优点是,如果调用者从不进行任何修改,则无需创建私有副本。

这也是COW常用用法的应用程序:

COW概念还用于维护Microsoft SQL Server 2005等数据库服务器上的即时快照。即时快照通过在底层数据更新时存储数据的修改前副本来保留数据库的静态视图。即时快照用于测试用途或与时间相关的报告,并且不应用于替换备份。


任何将常规数组用于...的方法,但是,在某些情况下,这种类型的策略会导致更优化的结果。
Andrew Flanagan

3
@hhafez:Linux在clone()用于实现时会使用它fork()-子进程的父进程的内存是COWed的。
Kerrek SB 2012年

@hhafez某些文件系统使用CoW,例如BTRFS
Geremia

这是SandboxIE的工作方式吗?当沙盒程序想要覆盖某些内容时,sandboxie会拦截文件系统操作并将文件复制到沙盒文件夹中,并让程序写入沙盒文件而不是原始文件。那叫做写时复制吗?
罗尼·马修斯

合并最终如何发生?如果有N个副本,最终保留哪一个副本保存在磁盘上?
SimpleGuy

59

“写时复制”听起来或多或少意味着:每个人都有相同数据的单个共享副本,直到被写入为止,然后创建一个副本。通常,写时复制用于解决并发问题。例如,在ZFS中,磁盘上的数据块被分配为写时复制。只要没有变化,就保留原始块;更改仅更改了受影响的块。这意味着分配了最少数量的新块。

这些更改通常也实现为事务性的,即它们具有ACID属性。这消除了一些并发问题,因为这样可以保证所有更新都是原子更新。


1
如果您进行更改,如何通知另一方您的新副本?他们不会看到错误的数据。
powder366 2015年

12
@ powder366-不,他们不会看到错误的数据,因为当您进行更改时,实际上是在进行复制。例如,您有一个名为的数据块A。过程中1234每个想拍它的一个副本,并开始阅读它,在“写时复制”系统没有被复制但一切都还在读书A。现在,process 3想要对其副本进行更改A,process 3实际上将对其进行副本A并创建一个名为的新数据块B。过程124还在读块A过程3现在读取B
Puddler '16

1
@Puddler如果在'A'中进行更改,将会发生什么。所有进程都将读取更新的信息还是旧的?
开发人员

3
@Developer:那么无论对哪个过程进行更改,A都应该创建一个新副本。如果您问一个全新的过程来了并且会发生变化,A那会发生什么,那么我的解释就没有足够详细地说明了。这将是实现特定的,需要你是如何想的实施工作,如文件\数据锁定等其余知识
Puddler

10

我不会在写复制时重复相同的答案。我认为安德鲁的答案查理的答案已经很清楚了。我将为您提供来自OS世界的示例,仅提及此概念的使用范围。

我们可以使用fork()vfork()创建一个新的过程。vfork遵循写时复制的概念。例如,由vfork创建的子进程将与父进程共享数据和代码段。这样可以加快分叉时间。如果要先执行exec,然后执行vfork,则应使用vfork。因此,vfork将创建子进程,该子进程将与其父进程共享数据和代码段,但是当我们调用exec时,它将在子进程的地址空间中加载新可执行文件的映像。


3
“ vfork遵循写时复制的概念”。请考虑更改此行。vfork不使用COW。实际上,如果孩子写东西,可能会导致不确定的行为,并且无法复制页面!实际上,您可以说相反的说法是正确的。COW的行为就像vfork在共享空间中对某些内容进行修改之前一样!
Pavan Manjunath'3

完全同意Pavan。删除“ vfork遵循写时复制的概念”行。如今,COW在fork中用作优化,因此它的作用类似于vfork,并且不会为子进程复制父级数据(如果我们仅在子进程中调用exec *)
Shekhar Kumar,2013年

7

仅举另一个例子,Mercurial使用写时复制使克隆本地存储库成为真正的“廉价”操作。

除了要谈论物理文件而不是内存中的对象外,该原理与其他示例相同。最初,克隆不是重复的,而是与原始副本的硬链接。在克隆中更改文件时,将写入副本以代表新版本。


2

我发现这个在PHP约zval的好文章,里面提到过COW:

写时复制(缩写为“ COW”)是一种旨在节省内存的技巧。它在软件工程中更普遍地使用。这意味着,当您写入符号时,如果PHP已经指向zval,则PHP将复制该内存(或分配新的内存区域)。


0

在Ruby'Enterprise Edition'中也使用它作为节省内存的一种巧妙方法。


2
我不认为他的意思是“用于”。
spydon 2015年

0

一个很好的例子是Git,它使用一种策略来存储Blob。为什么使用散列?一方面是因为它们更易于执行差异化处理,另一方面是因为它使优化COW策略更简单。当您使用很少的文件进行新提交时,绝大多数对象和树都不会改变。因此,提交将通过由哈希组成的各种指针引用一堆已经存在的对象,从而使存储整个历史记录所需的存储空间小得多。


0

这是一个内存保护概念。在此编译器中,将创建额外的副本以修改子级中的数据,并且此更新的数据不会反映在父级数据中。


0

以下是使用装饰器设计模式的写时复制(COW)Python实现。Value可变CowValue对象(装饰器)持有对不变对象的引用。该CowValue对象将所有读取请求转发到不可变Value对象,并通过创建Value具有正确状态的新不可变对象来拦截所有写请求。该CowValue对象必须变数之间的浅拷贝到允许的共享Value对象。

import abc
import copy

class BaseValue(abc.ABC):
    @abc.abstractmethod
    def read(self):
        raise NotImplementedError
    @abc.abstractmethod
    def write(self, data):
        raise NotImplementedError

class Value(BaseValue):
    def __init__(self, data):
        self.data = data
    def read(self):
        return self.data
    def write(self, data):
        pass

class CowValue(BaseValue):
    def __init__(self, data):
        self.value = Value(data)
    def read(self):
        return self.value.read()
    def write(self, data):
        self.value = Value(data)

v = CowValue(1)
w = copy.copy(v)  # shares the immutable Value object
assert v.read() == w.read()
assert id(v.value) == id(w.value)
w.write(2)  # creates a new immutable Value object with the correct state
assert v.read() != w.read()
assert id(v.value) != id(w.value)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.