@zhuanxu
2018-05-24T12:14:50.000000Z
字数 1827
阅读 1507
c++
先定义什么叫线程安全:就是能够在多线程环境下使用,不需要通过额外的同步代码。
要保证对象的构造线程安全,我们需要做到在构造期间不将this指针泄露出去,因为在多线程环境下,有可能其他线程会使用到未构造完成的this指针。
即使我们在构造函数的最后一行,也不应该将this指针传递给其他线程,因为c++中基类早于派生类进行构造,此时有可能还要进行派生类的构造。
问题:如何保证一个对象正在使用时不会被其他线程析构。
我们知道解决竞态的方法就是将所有操作排队,其中一种方法就是加锁,但是在多线程中,锁有可能会在析构函数中被释放,来看例子:
客户端代码:
在实际执行中,可能在Foo析构函数得到mutex锁之前,线程B已经在执行update了,并且阻塞在锁上,此时析构函数执行完后,锁就不存在了。程序行为未知。。。
从上面的例子我们看到了作为成员变量的mutex是无法保护析构函数的。
上面的例子中,如果我们有一种好的方法能够判断一个指针是否可用,我们就能该对象的状态了。
在面向对象中,对象的关系主要有3种:
其中组合两者的生命周期一致,不存在什么问题;关联是一种比较弱的关系,形式就是在函数中调用了另外一个对象的方法,此时我们就无法知道该对象是否还存活;聚合关系也是,因为不拥有成员对象的生命周期,所以也无法判断是否存活。
所以上面这种问题要想解决,我们还是得从对象指针入手,我们需要智能指针。
我们先来回答一个问题:什么时候对象可以释放?
当没有人使用的时候就可以释放了,于是我们就可以使用带引用计数的指针来解决是否有人使用的问题。
c++11中带来了解决方案 shared_ptr 和 weak_ptr ,其中 shared_ptr 在原始指针的基础上增加了一个引用计数,只要有程序中有引用,对象就不析构;weak_ptr 是弱引用,并不增加 shared_ptr 的引用计数。
另外需要特别注意的 shared_ptr 和 weak_ptr 都是值语义,意味着是以下几种情况
对于 shared_ptr ,我们使用拷贝构造和拷贝赋值的时候增加引用计数,而使用移动赋值和移动构造的时候转出控制权。
在 shared_ptr 的文档中提到多线程环境下:
If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race
如果多线程环境下使用shared_ptr,并且同时会去使用其管理对象的非const方法,那就需要额外的同步代码。
我们现在有了 share_ptr,通过它我们就能够很好个管理对象指针了,下面是一个使用 share_ptr 的例子:
get的逻辑是从map中找key,如果不存在就创建后返回,上面代码的一个问题是,由于使用shared_ptr管理,由于容器的存在,stock对象一直不会被释放,于是我们就有了下面的 weak_ptr 版本。
容器中保存weak_ptr,返回到外部的 shared_ptr 能够释放掉内存,但是容器中的weak_ptr却一直存在,所以我们有了第3个版本:
在 创建shared_ptr的时候传入析构函数,让其删除weak_ptr和stock对象。
但是上面版本的问题是:由于我们将stockFactory的指针传递给了Functor对象, 我们不能保证 share_ptr 对象的生命周期比 stockFactory 长,如果 stockFactory 先于 share_ptr 析构,会有问题,所以我们就有第4个版本,我们给 Functor 传递 shared_ptr 指针:
上面版本还有一个小问题: stockFactory 的生命周期被 share_ptr 延长了,于是我们就有了最后的版本,通过 weak_ptr 来传出 stockFactory 指针:
我们可以看到c++中通过 shared_ptr 和 weak_ptr 能够很好的解决内存管理问题。
以上内容来自 Linux多线程服务端编程第一章。
你的鼓励是我继续写下去的动力,期待我们共同进步。