智能指针与多线程

目的:
智能指针在多线程编程场景下,可以保证对象安全地析构,解引用时对象有效。

本文中涉及到的具体内容可以参考linux多线程服务端编程一书的第一、二章。

小结:
这里汇总下书中的结论:

对象析构算是写操作。
借助shared_ptr来实现线程安全的对象释放,但是shared_ptr本身不是100%线程安全。所以多个线程中访问同一个shared_ptr也需要加锁保护。
shared_ptr会延长对象的生命周期,只要有一个指向x对象的shared_ptr,该对象就不会析构。
shared_ptr是值语义,当心意外延长对象的生命周期,例如bind和容器都可能拷贝shared_ptr。
weak_ptr是shared_ptr的好搭档,可以作弱回调、对象池等。
COW:
这里整理下书中使用智能指针实现写时复制的做法。使用shard_ptr来管理数据,原理如下:

shared_ptr是引用计数型智能指针,如果当前只有一个观察者,那么引用计数器值为1。
对于write端,如果发现计数为1,这时可以安全的修改共享对象,不必担心有人在读它。
对于read端,在读之前把引用计数加1,读完后减1,这样保证在读期间,引用计数大于1,保证读取时对象不会被析构,也可以阻止并发写。
对于write端,如果发现计数大于1,则需要复制。
全局数据定义:

// 全局数据;
typedef vector<Foo>  FooList;
typedef shared_ptr<FooList> FoolListPtr;
MutexLock mutex;
FoolListPtr g_foos; // 所有线程可见,访问时要加互斥锁;

读端:

/*
    临界区非常小只读了一次共享变量g_foos
    比以往的写法大为缩短,多个线程调用read也不会相互阻塞,提高了并发性;
*/
void read()
{
    FoolListPtr foos;  // 位于每个线程的栈空间,每个线程有自己的线程栈
    {
        MutexLockGuard lock(mutex);
        foos = g_foos; // 增加引用计数,确保读的时候对象不会被析构掉;
        assert(!g_foos.unique());
    }

    for (auto iter = g_foos->begin(); iter != g_foos->end(); ++iter)
    {
        iter->doit();
    }
}


写端:

void write(const Foo& f)
{
    MutexLockGuard lock(mutex);
    if (!g_foos.unique()) // 别的线程正在读取FooList,不能原地修改,复制一份,在副本基础上修改
    {
        // 由于不是g_foos.unique(),所以其指向对象始终有效。
        g_foos.reset(new FooList(*g_foos));
    }
    assert(g_foos.unique());
    g_foos->push_back(f);
}

习题:
本部分是书中对COW的一些错误做法:

错误一:直接修改g_foos所指的 FooList。

void write(const Foo& f)
{
  MutexLockGuard lock(mutex);
  g_foos->push_back(f);
}

如果线程A调用write;

线程B调用read,read正在访问FooList的一个迭代器;

由于线程A执行了push_back,导致read时迭代器失效。

错误二:试图缩小临界区,把copying移出临界区。

void write(const Foo& f)
{
  FooListPtr newFoos(new FooList(*g_foos)); // 访问全局对象没有加锁;
  newFoos->push_back(f);
  MutexLockGuard lock(mutex);
  g_foos = newFoos;
}

线程A调用g_foos.reset,如果此时引用计数为0,则FooList对象被释放;

线程B调用write,此时g_foos指向的对象已经被释放,拷贝一个已经释放的对象,程序崩溃;

错误三:把临界区拆分成两个小的,把copying放到临界区外。

void write(const Foo& f)
{
  FooListPtr oldFoos;
  {
    MutexLockGuard lock(mutex);
    oldFoos = g_foos;
  }
  // 以上代码保证了FooList不会被析构,从而拷贝的对象不会是一个被释放的对象;不会出现错误二提到的问题
  FooListPtr newFoos(new FooList(*oldFoos));
  newFoos->push_back(f);
  MutexLockGuard lock(mutex);
  g_foos = newFoos;
}

线程A调用write执行到FooListPtr newFoos(new FooList(*oldFoos));

线程B修改了g_foos所指的 FooList;

导致线程A复制的数据是坏数据。

参考文献:
智能指针基础总结
借助智能指针实现写时复制
linux多线程服务端编程 第一章, 2.8章节
陈硕 借shared_ptr实现copy-on-write

智能指针与多线程_智能指针多线程-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值