Ответы:
Я собирался написать свое собственное объяснение, но эта статья в Википедии в значительной степени подводит итог.
Вот основная концепция:
Копирование при записи (иногда называемое «COW») - это стратегия оптимизации, используемая в компьютерном программировании. Фундаментальная идея состоит в том, что если несколько вызывающих запрашивают ресурсы, которые изначально неотличимы, вы можете дать им указатели на один и тот же ресурс. Эта функция может поддерживаться до тех пор, пока вызывающий объект не попытается изменить свою «копию» ресурса, после чего будет создана настоящая частная копия, чтобы изменения не стали видимыми для всех остальных. Все это происходит прозрачно для вызывающих абонентов. Основное преимущество состоит в том, что, если вызывающий объект никогда не вносит никаких изменений, не нужно создавать личную копию.
Также вот пример общего использования COW:
Концепция COW также используется при обслуживании мгновенных снимков на серверах баз данных, таких как Microsoft SQL Server 2005. Мгновенные снимки сохраняют статическое представление базы данных, сохраняя копию данных до модификации при обновлении лежащих в основе данных. Мгновенные снимки используются для тестирования или отчетов, зависящих от момента, и не должны использоваться для замены резервных копий.
clone()
для реализации fork()
- память родительского процесса ЗАКРЫВАЕТСЯ для дочернего.
«Копировать при записи» означает более или менее то, на что это похоже: у всех есть одна общая копия одних и тех же данных до тех пор, пока они не будут записаны , а затем копия будет сделана. Обычно копирование при записи используется для решения проблем, связанных с параллелизмом. В ZFS , например, блоки данных на диске выделяются для копирования при записи; до тех пор, пока нет изменений, вы сохраняете исходные блоки; изменение изменило только затронутые блоки. Это означает, что выделяется минимальное количество новых блоков.
Эти изменения также обычно реализуются как транзакционные , т. Е. Они имеют свойства ACID . Это устраняет некоторые проблемы с параллелизмом, поскольку тогда вам гарантируется, что все обновления являются атомарными.
A
. Процесс 1
, 2
, 3
, 4
каждый хочет сделать копию и начать читать его, в «копировании при записи» система ничего не копируется пока все еще читают A
. Теперь процесс 3
хочет внести изменения в свою копию A
, 3
теперь процесс фактически сделает копию A
и создаст новый блок данных с именем B
. Процесс 1
, 2
, 4
все еще читает блок - A
процесс 3
в настоящее время чтения B
.
A
должен создавать новую копию. Если вы спрашиваете, что произойдет, если возникнет и изменится совершенно новый процесс, A
тогда мое объяснение не содержит достаточно подробностей для этого. Это будет зависеть от конкретной реализации и потребует знаний о том, как вы хотите, чтобы остальная часть реализации работала, например о блокировке файлов / данных и т. Д.
Я не буду повторять один и тот же ответ о копировании при записи. Я думаю , что ответ Эндрю и ответ Чарли уже очень ясно. Я приведу вам пример из мира ОС, просто чтобы отметить, насколько широко используется эта концепция.
Мы можем использовать fork()
или vfork()
создать новый процесс. vfork следует концепции копирования при записи. Например, дочерний процесс, созданный vfork, будет совместно использовать данные и сегмент кода с родительским процессом. Это ускоряет время разветвления. Ожидается, что будет использоваться vfork, если вы выполняете exec, а затем vfork. Таким образом, vfork создаст дочерний процесс, который будет совместно использовать данные и сегмент кода со своим родительским процессом, но когда мы вызываем exec, он загрузит образ нового исполняемого файла в адресное пространство дочернего процесса.
vfork
НЕ использует КОРОВУ. Фактически, если ребенок что-то напишет, это может привести к неопределенному поведению, а не к копированию страниц !! На самом деле, можно сказать, что это в некоторой степени верно. COW действует как будто vfork
что-то не модифицируется в общем пространстве!
Еще один пример: Mercurial использует копирование при записи, чтобы сделать клонирование локальных репозиториев действительно «дешевой» операцией.
Принцип такой же, как и в других примерах, за исключением того, что вы говорите о физических файлах, а не об объектах в памяти. Изначально клон - это не дубликат, а жесткая ссылка на оригинал. Когда вы изменяете файлы в клоне, создаются копии, представляющие новую версию.
Я нашел эту хорошую статью о zval в PHP, в которой также упоминается COW:
Копирование при записи (сокращенно COW) - это уловка, предназначенная для экономии памяти. Обычно он используется в разработке программного обеспечения. Это означает, что PHP будет копировать память (или выделять новую область памяти) при записи в символ, если этот уже указывал на zval.
Хорошим примером является Git, который использует стратегию хранения больших двоичных объектов. Почему он использует хеши? Отчасти потому, что с ними легче выполнять различия, но также потому, что упрощает оптимизацию стратегии COW. Когда вы делаете новую фиксацию с несколькими изменениями файлов, подавляющее большинство объектов и деревьев не изменится. Следовательно, фиксация через различные указатели, состоящие из хэшей, будет ссылаться на группу объектов, которые уже существуют, что значительно уменьшит пространство для хранения, необходимое для хранения всей истории.
Ниже представлена реализация Python с копированием при записи (COW) с использованием шаблона проектирования декоратора . Ссылка на неизменяемый 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)