一、什么是写时拷贝

写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本给该调用者,而其他调用者所见到的最初的资源仍然保持不变。


如果后续没有对资源进行修改的操作,甚至不会进行数据拷贝,如果在fork函数返回之后,马上调用exec()函数,也不会有数据拷贝

exec函数的作用是让子进程执行其它程序,即替换当前进程的映像

 
 
此作法的主要优点是如果调用者没有修改该资源,就不会有副本被建立,因此多个调用者只是读取操作是可以共享同一份资源。
 
写时拷贝技术是一种很重要的优化手段,核心是懒惰处理实体资源请求,在多个实体资源之间只是共享资源,起初是并不真正实现资源拷贝,
 
 
 
C++写时拷贝

 

 

 
只有当实体有需要对资源进行修改时才真正为实体分配私有资源。但写时拷贝技术技术也有它的优点和缺点
 
1、写时拷贝技术可以减少分配和复制大量资源时带来的瞬间延时,但实际上是将这种延时附加到了后续的操作之中。
 
2、写时拷贝技术可以减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。
 
 
二、写时拷贝技术的两种实现方案
 
当一个内存空间被多个对象引用的时候,如果这些对象没有对内存空间进行修改,则不进行内存的拷贝,有对象对内存空间进行修改的时候,检查是否有除自己外别的对象使用这块空间,若有,则自己重新开辟空间进行更改,不影响其他对象;若没有其他对象使用此空间,则说明只有自己使用此空间,就进行更改。此时引入引用计数,用于统计有多少对象在使用此空间。
 
写时复制两种实现方案的区别在于,引用计数是否要开辟一个新的空间去保存,还是保存在拷贝地址的首部(数据首地址往后偏移4个字节)

 

方案一:

如下图所示:

C++写时拷贝

 

 

当创建一个对象s1,再拷贝构造一个对象s2时,引用计数_refCount自动加1,此时为2。若此时s2要对自身进行修改,则s2重新开辟一块空间,检查引用计数_refCount是否大于1,若大于1,,则进行修改,这样就不会影响其他对象。

C++写时拷贝

 

 

s2重新开辟一块儿空间,再修改s1原来空间的引用计数_refCount,使其减1,重新开辟的空间中因为此时只有s2一个对象,所以引用计数_refCount也为1.

 

小结:此方案的写时拷贝计数是同时开辟两块空间,一个自身存放的内容,一个存放引用计数_refCount,同时管理两块空间,统计当前使用此空间的对象数,当要修改当前空间的时候,进行对引用计数的判断,再决定是否开辟新的空间。

代码如下:

class String
{
public:
    //构造函数
    String(char* str="")
        :_str(new char[strlen(str)+1])
        ,_refCount(new int(1))
    {
        strcpy(_str, str);
    }
 
 
    //拷贝构造函数
    String(String& s)
    {
        _str = s._str;
        _refCount = s._refCount;
        ++(*_refCount);
    }
 
 
    String& operator=(const String& s)
    {
        if (_str != s._str)
        {
            Release();
 
            _str = s._str;
            _refCount = s._refCount;
            (*_refCount)++;
        }
        return *this;
    }
 
 
    void Release()
    {
        if (--(*_refCount) == 0)
        {
            cout << "delete" << endl;
            delete[] _str;
            delete _refCount;
        }
    }
 
    ~String()
    {
        Release();
    }
 
    void CopyOnWrite()
    {
        if (*_refCount > 1)
        {
            char* tmp = new char[strlen(_str) + 1];
            strcpy(tmp, _str);
            --(*_refCount);
 
            _str = tmp;
            _refCount = new int(1);
        }
    }
 
    char& operator[](size_t pos)
    {
        CopyOnWrite();
        return _str[pos];
    }
 
    char operator[](size_t pos) const
    {
        return _str[pos];
    }
private:
    char* _str;
    int* _refCount;
};
View Code

方案1中是开辟了两块空间进行管理,方案2采用开辟一块空间进行写时拷贝的操作。

开辟一块空间,在这块空间的头4个字节中放置引用计数,真正存放内容的空间从第5个字节开始。一次性多开辟4个字节进行写时拷贝的操作。

具体如下图所示:

C++写时拷贝

 

 当进行操作时,先检查引用计数的个数,然后进行判断是否开辟新的空间,同时修改引用计数的值,防止空间不能释放。

具体例子如下:
当创建了3个对象,s1,s2,s3同时指向一个空间,此时引用计数为3,再创建1个对象s4,s4的引用计数为1。

C++写时拷贝

 

 再进行操作s3 = s4;此时对应的引用计数和对象的指向都需要更改,更改之后如下图所示:

C++写时拷贝

 

 

 对象s3指向了s4,同时s3原来的空间的引用计数进行减1,新指向空间的引用计数进行加1.

 

小结:方案2的写时拷贝计数使用一块空间进行内容和引用计数的管理和操作,不开辟两块空间,方便管理。

代码如下:

class String
{
public:
    String(const char* str)
        :_str(new char[strlen(str) + 5])
    {
        _str += 4;
        strcpy(_str, str);
        GetRefCount();
    }
 
 
    String(const String& s)
        :_str(s._str)
    {
        ++GetRefCount();
    }
 
    ~String()
    {
        if (--GetRefCount() == 0)
        {
            cout << "delete" << endl;
            delete[](_str - 4);
        }
    }
 
    String& operator=(const String& s)
    {
        if (_str != s._str)
        {
            if (--GetRefCount() == 0)
            {
                delete[](_str - 4);
            }
 
            _str = s._str;
            GetRefCount()++;
        }
        return *this;
    }
 
    int& GetRefCount()
    {
        return *((int*)(_str - 4));
    }
 
    char& operator[](size_t pos)
    {
        CopyOnWrite();
        return _str[pos];
    }
 
    void CopyOnWrite()
    {
        if (GetRefCount()>1)//当一块空间有两个或者两个以上的对象指向时,才写时拷贝
        {
            char* tmp = new char[strlen(_str) + 5];
            strcpy(tmp, _str);
            --GetRefCount();
            _str = tmp;
            GetRefCount() = 1;
        }
    }
 
private:
    char* _str;
    int* _refCount;
};
View Code

相关文章:

  • 2021-04-22
  • 2021-10-06
  • 2022-12-23
  • 2021-11-23
  • 2022-02-08
  • 2021-08-16
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-11-09
  • 2021-11-22
  • 2021-08-01
  • 2021-08-22
相关资源
相似解决方案