shared_ptr实现copy_on_write
参考:《linux多线程服务器编程---使用module网络库》(陈硕) 第二章 线程同步精要(P53-55)。
在多线程编程中,如果要用到修改共享资源的地方,如何正确地解决问题并提高效率?
#include"mutex.h"#include<vector>#include<string>MutexLock mutex;classFoo{public:void doit();};std::vector<Foo> foos;void post(constFoo&f){MutexLockGuard lock(&mutex);foos.push_back(f);}void traverse(){MutexLockGuard lock(&mutex);std::vector<Foo>::iterator iter=foos.begin();for(; iter!=foos.end(); iter++){iter->doit();}}voidFoo::doit(){Foo f;post(f);}int main(){Foo f;post(f);traverse();}
如上面的代码中,由于在traverse()中调用了doit()函数,而doit()中又调用了post,这两个函数里面都有锁存在,如何解决问题?
#include"mutex.h"#include<vector>#include<string>#include<memory>MutexLock mutex;classFoo{public:void doit();};typedef std::vector<Foo>FooList;typedef std::shared_ptr<std::vector<Foo>>FooListPtr;FooListPtr g_foos(new std::vector<Foo>);void post(constFoo&f){MutexLockGuard lock(&mutex);if(!g_foos.unique()){g_foos.reset(newFooList(*g_foos));}g_foos->push_back(f);}void traverse(){FooListPtr local_foos;{MutexLockGuard lock(&mutex);local_foos =g_foos;}std::vector<Foo>::iterator iter=local_foos->begin();for(; iter!=local_foos->end(); iter++){iter->doit();}}voidFoo::doit(){Foo f;post(f);}int main(){Foo f;post(f);traverse();}
这里使用了copy_on_write办法来解决这个问题,就是写时拷贝的方法。
第一处:
post()函数中,这里是往vector里面插入新的对象,这有可能破坏迭代器,引起在traverse的时候崩溃。于是它通过判读是否有
其它地方在引用该vector,也就是:
if(!g_foos.unique())
如果有其它地方还在引用,那就把g_foos重置指向新的地址,旧的地址就在被其它地方使用,当被使用完以后就自动释放,因为引用计数减0了,而且用了reset()。
后面就使用新的newFooList(*g_foos)。
第二处:
也就是在traverse中,
MutexLockGuard lock(&mutex);local_foos =g_foos;
这里使用了一个临时的变量来引用g_foos,这样就可以使得原来被引用的计数加1。标志有其它地方在使用g_foos。
使用写时拷贝的方法(copy_on_write),就解决了一些必须递归调用锁的问题。这种思想使用得比较广泛,如流表的管理就可以,首先将读写分离,将一个线程里面修改流表,转发线程都只读。
shared_ptr 的引用计数本身是安全且无锁的,但对象的读写则不是.
一个 shared_ptr 对象实体可被多个线程同时读取;
两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
浙公网安备 33010602011771号