shared_ptr实现copy_on_write

参考:《linux多线程服务器编程---使用module网络库》(陈硕)  第二章 线程同步精要(P53-55)。
    

在多线程编程中,如果要用到修改共享资源的地方,如何正确地解决问题并提高效率?

  1. #include"mutex.h"
  2. #include<vector>
  3. #include<string>
  4. MutexLock mutex;
  5. classFoo{
  6. public:
  7. void doit();
  8. };
  9. std::vector<Foo> foos;
  10. void post(constFoo&f){
  11. MutexLockGuard lock(&mutex);
  12. foos.push_back(f);
  13. }
  14. void traverse(){
  15. MutexLockGuard lock(&mutex);
  16. std::vector<Foo>::iterator iter=foos.begin();
  17. for(; iter!=foos.end(); iter++){
  18. iter->doit();
  19. }
  20. }
  21. voidFoo::doit(){
  22. Foo f;
  23. post(f);
  24. }
  25. int main(){
  26. Foo f;
  27. post(f);
  28. traverse();
  29. }
如上面的代码中,由于在traverse()中调用了doit()函数,而doit()中又调用了post,这两个函数里面都有锁存在,如何解决问题?

  1. #include"mutex.h"
  2. #include<vector>
  3. #include<string>
  4. #include<memory>
  5. MutexLock mutex;
  6. classFoo{
  7. public:
  8. void doit();
  9. };
  10. typedef std::vector<Foo>FooList;
  11. typedef std::shared_ptr<std::vector<Foo>>FooListPtr;
  12. FooListPtr g_foos(new std::vector<Foo>);
  13. void post(constFoo&f){
  14. MutexLockGuard lock(&mutex);
  15. if(!g_foos.unique()){
  16. g_foos.reset(newFooList(*g_foos));
  17. }
  18. g_foos->push_back(f);
  19. }
  20. void traverse(){
  21. FooListPtr local_foos;
  22. {
  23. MutexLockGuard lock(&mutex);
  24. local_foos =g_foos;
  25. }
  26. std::vector<Foo>::iterator iter=local_foos->begin();
  27. for(; iter!=local_foos->end(); iter++){
  28. iter->doit();
  29. }
  30. }
  31. voidFoo::doit(){
  32. Foo f;
  33. post(f);
  34. }
  35. int main(){
  36. Foo f;
  37. post(f);
  38. traverse();
  39. }
这里使用了copy_on_write办法来解决这个问题,就是写时拷贝的方法。
第一处:
    post()函数中,这里是往vector里面插入新的对象,这有可能破坏迭代器,引起在traverse的时候崩溃。于是它通过判读是否有
其它地方在引用该vector,也就是:
  1. if(!g_foos.unique())
如果有其它地方还在引用,那就把g_foos重置指向新的地址,旧的地址就在被其它地方使用,当被使用完以后就自动释放,因为引用计数减0了,而且用了reset()。
后面就使用新的newFooList(*g_foos)。
第二处:
    也就是在traverse中,
  1. MutexLockGuard lock(&mutex);
  2. local_foos =g_foos;
这里使用了一个临时的变量来引用g_foos,这样就可以使得原来被引用的计数加1。标志有其它地方在使用g_foos。

使用写时拷贝的方法(copy_on_write),就解决了一些必须递归调用锁的问题。这种思想使用得比较广泛,如流表的管理就可以,首先将读写分离,将一个线程里面修改流表,转发线程都只读。

shared_ptr 的引用计数本身是安全且无锁的,但对象的读写则不是.
一个 shared_ptr 对象实体可被多个线程同时读取;
两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。
 
 
 
 
 





原文地址:https://www.cnblogs.com/yml435/p/6929888.html