COW(Copy-On-Write)通过浅拷贝(shallow copy)只复制引用而避免复制值;当的确需要进行写入操作时,首先进行值拷贝,再对拷贝后的值执行写入操作,这样减少了无谓的复制耗时。
特点如下:这里的安全指在进行读取或者写入的过程中,数据不被修改。
copy-on-write最擅长的是并发读取场景,即多个线程/进程可以通过对一份相同快照,去处理实效性要求不是很高但是仍然要做的业务,如Unix下的fork()系统调用、标准C++类std::string等采用了 copy-on-write,在真正需要一个存储空间时才去分配内存,这样会极大地降低程序运行时的内存开销。
Copy-On-Write的原理:即引用计数用来解决用来存放字符串的内存何时释放的问题。
COW技术的精髓:写时复制(Copy-On-Write)技术,是编程界"懒惰行为"-拖延战术的产物。
shared_ptr是采用引用计数方式的智能指针,如果当前只有一个观察者,则其引用计数为1,可以通过shared_ptr::unique()判断。用shared_ptr来实现COW时,主要考虑两点:(1)读数据 (2)写数据
通过shared_ptr实现copy-on-write的原理如下:
1. read端在读之前,创建一个新的智能指针指向原指针,这个时候引用计数加1,读完将引用计数减1,这样可以保证在读期间其引用计数大于1,可以阻止并发写。
//假设g_ptr是一个全局的shared_ptr并且已经初始化。
void read()
{
shared_ptr tmpptr;
{
lock();
tmpptr=g_ptr;//此时引用计数为2,通过gdb调试可以看到
}
//访问tmpptr
//...
}
这部分是shared_ptr最基本的用法,还是很好理解的,read()函数调用结束,tmpptr作为栈上变量离开作用域,自然析构,原数据对象的引用计数也变为1。
void write()
{
lock()
if(!g_ptr.unique())
{
g_ptr.reset(new Foo(*g_ptr));
}
assert(g_ptr.unique());
//write
//
}
解释一下代码:
接着讲讲,《Linux多线程服务端编程—使用muduo C++网络库》的2.8节中的三种错误写法。
错误一:直接修改g_foos所指的 FooList
void post(const Foo& f)
{
MutexLockGuard lock(mutex);
g_foos->push_back(f);
}
如果有别的地方用到g_foos所指的 FooList的某一个迭代器,由于post()函数的push_bak()导致迭代器失效。
void post(const Foo& f)
{
FooListPtr newFoos(new FooList(*g_foos));
newFoos->push_back(f);
MutexLockGuard lock(mutex);
g_foos = newFoos;
}
临界区前的两行代码都是线程不安全的。
void post(const Foo& f)
{
FooListPtr oldFoos;
{
MutexLockGuard lock(mutex);
oldFoos = g_foos;
}
FooListPtr newFoos(new FooList(*oldFoos));
newFoos->push_back(f);
MutexLockGuard lock(mutex);
g_foos = newFoos;
}
新建oldFoos指向原指针,防止被别的线程析构。但在这一行:FooListPtr newFoos(new FooList(*oldFoos)); ,如果有别的线程在修改g_foos所指的 FooList呢,后果可想而知。
typedef std::set RequestList;
typedef boost::shared_ptr RequestListPtr;
RequestListPtr requests_;
修改Inventory类的add()和remove()这两个成员函数
void add(Request* req)
{
muduo::MutexLockGuard lock(mutex_);
if (!requests_.unique())
{
requests_.reset(new RequestList(*requests_));
printf("Inventory::add() copy the whole list\n");
}
assert(requests_.unique());
requests_->insert(req);
}
void remove(Request* req) // __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
if (!requests_.unique())
{
requests_.reset(new RequestList(*requests_));
printf("Inventory::remove() copy the whole list\n");
}
assert(requests_.unique());
requests_->erase(req);
}
class Request : public boost::enable_shared_from_this
{
public:
Request()
: x_(0)
{
}
~Request()
{
x_ = -1;
}
void cancel() __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
x_ = 1;
sleep(1);
printf("cancel()\n");
g_inventory.remove(shared_from_this());
}
void process() // __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
g_inventory.add(shared_from_this());
// ...
}
void print() const __attribute__ ((noinline))
{
muduo::MutexLockGuard lock(mutex_);
// ...
printf("print Request %p x=%d\n", this, x_);
}
private:
mutable muduo::MutexLock mutex_;
int x_;
};