Copy On Write
“在写的时候复制”
概念
“写入时复制(英语:Copy-on-write,简称COW)是一种计算机 [程序设计]领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是 [透明]的。此做法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy) 被创建,因此多个调用者只是读取操作时可以共享同一份资源。”
当多个调用者需要使用同一份资源时,最简单的方法是为新来的调用者创建一份资源副本,但是创建副本会带来额外开销。当调用者过多时,创建副本的开销也会变大,为了减少这部分系统开销,对调用者做了标记,判断调用者究竟是要读还是要写,如果是读就和之前的调用者共享资源,如果是写才创建资源副本。COW翻译过来可以是“在写的时候复制”。
下面是整理的几种COW的应用场景
Fork系统调用
fork方法最初是Unix系统中创建子进程的系统调用方法,在创建子进程时会使用Fork方法。假如进程只有读的事件,那么父子进程使用相同的代码段数据段,只有发生写事件时,系统才会拷贝给子进程一份资源。
写时复制的主要优势在于它可以显著减少内存使用,特别是在拷贝大规模数据时。它避免了不必要的数据复制,从而提高了系统性能和效率。此外,写时复制还提供了更好的并发性,因为多个进程或线程可以同时读取相同的数据副本,而无需担心冲突。
关于fork系统调用可以阅读我的另一篇笔记
Redis持久化
Redis的RDB持久化:bgsave命令执行后,会在服务端目录下生成一个dump.rdb文件,而这个文件中就保存了内存中存放的数据,当服务器重启后,会自动加载里面的内容到对应数据库中。
Redis是如何在持久化期间还能保证正常使用呢?它就使用了fork函数创建了子进程,在子进程中备份数据库
当 fork 操作执行后,父进程中的内存页都会被设置为只读,然后子进程的地址空间指向父进程。
当父子进程都只读内存时,共享资源,但是其中某个进程写内存时,CPU 硬件检测到内存页是只读的,于是触发页异常中断(page-fault)。中断例程中,系统就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。这确保了子进程中的数据是隔离的,不受父进程中数据更改的影响。
MySQL多版本并发控制(MVCC)
在MySQL的InnoDB引擎中多版本控制MVCC(Multiversion Concurrency Control)就用了CopyOnWrite原理。
COW策略在MVCC中的应用主要体现在版本链的维护中。当事务对某个数据项进行修改时,会创建该数据的新版本,并在新版本上进行修改操作。这样,旧版本的数据仍然可用于其他事务的读取,从而实现了多版本的并发控制。
Java - COWArrayList
支持写时复制的集合:CopyOnWriteArrayList
、CopyOnWriteArraySet
COW 容器固然安全了很多,但是由于每次写都要复制整个数组,时间和空间的开销都更高,因此只适合读多写少的情景。在写入时,为了保证效率,也应尽量做批量插入或删除,而不是单条操作。并且它的正本和副本有可能不同步,因此无法保证读取的是最新数据,只能保证最终一致性。