参考:《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()。
后面就使用新的 new FooList (* 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 对象,那么需要加锁。