什么是写时复制?

hha*_*fez 108 copy-on-write data-structures

我想知道什么是copy-on-write是什么以及它用于什么?Sun JDK教程中多次提到术语"写时复制数组",但我不明白它的含义.

And*_*are 127

我打算写下我自己的解释,但这篇维基百科的文章几乎总结了一下.

这是基本概念:

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

这里还有一个COW常用的应用:

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

  • @hhafez:Linux 在使用 `clone()` 来实现 `fork()` 时使用它 - 父进程的内存为子进程被 COWed。 (3认同)

Cha*_*tin 51

"写入时复制"或多或少意味着它的含义:每个人都有相同数据的单一共享副本,直到它被写入,然后复制.通常,copy-on-write用于解决并发类问题.例如,在ZFS中,磁盘上的数据块被分配为写时复制; 只要没有变化,你保留原始块; 更改仅更改了受影响的块.这意味着分配了最小数量的新块.

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

  • @ powder366 - 不,他们不会看到错误的数据,因为当您进行实际制作副本时的更改.例如,您有一个名为"A"的数据块.进程"1","2","3","4"各自想要复制它并开始阅读它,在"写入时复制"系统中没有任何东西被复制,但一切仍在读"A".现在进程`3`想要更改它的'A`副本,进程`3`现在实际上会复制`A`并创建一个名为`B`的新数据块.进程"1","2","4"仍在读取块"A"进程"3"现在正在读取"B". (11认同)
  • @Developer:嗯,无论对A进行任何更改,都应该创建一个新副本。如果您问如果出现一个全新的过程并更改了“ A”会发生什么情况,那么我的解释并没有真正涉及到足够的细节。那将是特定于实现的,并且需要有关您希望其余实现如何工作的知识,例如文件\数据锁定等。 (3认同)
  • 如果您进行更改,其他人如何收到您的新副本的通知?他们不会看到错误的数据吗? (2认同)

Sha*_*mik 9

我不会在Copy-on-Write上重复相同的答案.我认为安德鲁的回答查理的回答已经非常明确了.我将从OS世界给你一个例子,只是提到这个概念的使用范围有多广泛.

我们可以使用fork()vfork()创建一个新流程.vfork遵循copy-on-write的概念.例如,vfork创建的子进程将与父进程共享数据和代码段.这加快了分叉时间.如果您正在执行exec,然后执行vfork,则应使用vfork.因此,vfork将创建子进程,该进程将与其父进程共享数据和代码段,但是当我们调用exec时,它将在子进程的地址空间中加载新可执行文件的映像.

  • "vfork遵循写时复制的概念".请考虑更改此行.`vfork`不使用COW.事实上,如果孩子写东西,它可能导致未定义的行为,而不是复制页面!事实上,你可以说另一种方式是有道理的.COW就像`vfork`一样,直到在共享空间中修改了某些内容! (2认同)

Mag*_*ero 8

Erich Gamma 等人的《设计模式:可重用面向对象软件的元素》一书。清楚地描述了写时复制优化(\xe2\x80\x98Consequences\xe2\x80\x99 节,\xe2\x80\x98Proxy\xe2\x80\x99 章):

\n
\n

代理模式在访问对象时引入了一定程度的间接性。\n 额外的间接寻址有多种用途,具体取决于\n代理的类型:

\n
    \n
  1. 远程代理可以隐藏对象驻留在不同地址空间的事实。
  2. \n
  3. 虚拟代理可以执行优化,例如按需创建对象。
  4. \n
  5. 保护代理和智能引用都允许在访问对象时执行额外的内务处理任务。
  6. \n
\n

代理模式可以对客户端隐藏\xe2\x80\x99 的另一种优化。它\xe2\x80\x99s称为写时复制,它\xe2\x80\x99s与按需创建相关。复制大型且复杂的对象可能是一项昂贵的操作。\n 如果副本从未被修改,则\xe2\x80\x99s不需要\n产生此成本。通过使用代理来推迟复制过程,我们\n确保只有在对象\xe2\x80\x99s\n被修改时我们才支付复制对象的代价。

\n

要使写时复制工作,必须对主题进行引用计数。\n复制代理只会增加此引用\n计数。仅当客户端请求修改主题的操作时,代理才会实际复制它。在这种情况下,代理还必须\n减少主题\xe2\x80\x99s 引用计数。当引用计数变为零时,主题将被删除。

\n

写入时复制可以显着降低复制重量级主题的成本。\n

\n
\n

下面是使用代理模式的写时复制优化的 Python 实现。此设计模式的目的是为另一个对象提供代理来控制对其的访问。

\n

Proxy模式的类图:

\n

Proxy模式的类图

\n

代理模式的对象图:

\n

代理模式的对象图

\n

首先我们定义主题的接口:

\n
import abc\n\n\nclass Subject(abc.ABC):\n\n    @abc.abstractmethod\n    def clone(self):\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def read(self):\n        raise NotImplementedError\n\n    @abc.abstractmethod\n    def write(self, data):\n        raise NotImplementedError\n
Run Code Online (Sandbox Code Playgroud)\n

接下来我们定义实现主题接口的真实主题:

\n
import copy\n\n\nclass RealSubject(Subject):\n\n    def __init__(self, data):\n        self.data = data\n\n    def clone(self):\n        return copy.deepcopy(self)\n\n    def read(self):\n        return self.data\n\n    def write(self, data):\n        self.data = data\n
Run Code Online (Sandbox Code Playgroud)\n

最后我们定义实现主题接口并引用真实主题的代理:

\n
class Proxy(Subject):\n\n    def __init__(self, subject):\n        self.subject = subject\n        try:\n            self.subject.counter += 1\n        except AttributeError:\n            self.subject.counter = 1\n\n    def clone(self):\n        return Proxy(self.subject)  # attribute sharing (shallow copy)\n\n    def read(self):\n        return self.subject.read()\n\n    def write(self, data):\n        if self.subject.counter > 1:\n            self.subject.counter -= 1\n            self.subject = self.subject.clone() # attribute copying (deep copy)\n            self.subject.counter = 1\n        self.subject.write(data)\n
Run Code Online (Sandbox Code Playgroud)\n

然后,客户端可以通过使用代理作为真实主题的替身,从写时复制优化中受益:

\n
if __name__ == \'__main__\':\n    x = Proxy(RealSubject(\'foo\'))\n    x.write(\'bar\')\n    y = x.clone()  # the real subject is shared instead of being copied\n    print(x.read(), y.read())  # bar bar\n    assert x.subject is y.subject\n    x.write(\'baz\')  # the real subject is copied on write because it was shared\n    print(x.read(), y.read())  # baz bar\n    assert x.subject is not y.subject\n
Run Code Online (Sandbox Code Playgroud)\n


har*_*rpo 6

仅举几个例子,Mercurial使用copy-on-write使克隆本地存储库成为一种非常"便宜"的操作.

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