当一个对象能被多个线程同时看到时,那么对象的销毁时机就会变得模糊不清,可能出现多种竞态条件(对象析构和成员函数可能同时发生)。
C++标准库里的大多数class都不是线程安全的,包括std:: string、std::vector、std::map等,因为这些class通常需要在外部加锁才能供多个线程同时访问。
1 线程安全的对象创建
对象构造要做到线程安全,唯一的要求是在构造期间不要泄露**this**指针,因为构造期间对象没有完成初始化,半成品对象不能被其他线程访问(结果未知)。具体规则为:
- 不要在构造函数中注册任何回调函数;
- 不要在构造函数中把this传给跨线程的对象,便在构造函数的最后一行也不行
//正确的示例:二段式的构造
class Foo : public Observer //举例观察者模式
{
public:
Foo();//干净的构造函数,不返回或赋值this指针
void observe(Observer* s)
{
s->register(this);//这里已构造完,可以赋值
}
};
//使用
Foo* foo = new Foo();
Observer s = getSubject();
foo->observe(s);//这里才开始使用foo,早已构造完
2 线程安全销毁对象与智能指针
shared_ptr<T>是一个类模板(class template),它只有一个类型参数,使用起来很方便。引用计数是自动化资源管理的常用手法,当引用计数降为0时,对象(资源)即被销毁。shared_ptr控制对象的生命期。shared_ptr是强引用(想象成用铁丝绑住堆上的对象),只要有一个指向x对象的shared_ptr存在,该x对象就不会析构。当指向对象x的最后一个shared_ptr析构或reset()的时候,x保证会被销毁。
shared_ptr的拷贝开销比拷贝原始指针要高,但是需要拷贝的时候并不多。多数情况下它可以以const reference方式传递给函数。
weak_ptr也是一个引用计数型智能指针,但是它不增加对象的引用次数,即弱(weak)引用。weak_ptr不控制对象的生命期,但是它知道对象是否还活着(想象成用棉线轻轻拴住堆上的对象)。“提升/lock()”行为是线程安全的。
- 如果对象还活着,那么它可以提升(promote)为有效的shared_ptr;
- 如果对象已经死了,提升会失败,返回一个空的shared_ptr。
对象的析构是同步的,当最后一个指向x的shared_ptr离开其作用域的时候,x会同时在同一个线程析构。这个线程不一定是对象诞生的线程。
2.1 C++可能的内存问题
C++可能出现的内存问题分为如下几类:
内存问题 | 解决方法 |
---|---|
缓冲区溢出 | 使用vector或string管理缓冲区,自动记住缓冲区大小,使用成员函数修改,不直接使用指针 |
野指针 | 使用智能指针 |
重复释放 | 使用scoped_ptr、智能指针或RAII,只在对象析构时释放一次 |
内存泄露 | 使用scoped_ptr、智能指针或RAII,只在对象析构时自动释放 |
不配对的new 、delete | 把new/delete 替换成vector或scoped_array |
内存碎片 | 后面记录,见 |
2.2 shared_ptr是否线程安全?
shared_ptr本身不是100%线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为shared_ptr有两个数据成员,读写操作不能原子化 。要在多个线程访问同一shared_ptr(指智能指针本身,不是内部指向的数据),需要用mutex保护。
void read()
{
std::shared_ptr<Foo> localPtr;
{//使用临界区,读写在临界区外,缩短了锁的粒度
std::lock_guard<std::mutex> lock<mutex>;
localPtr = globalPtr;
}
doSomething(localPtr);//读写localPtr,不是需要保护的globalPtr
}
void write()
{
std::shared_ptr<Foo> localPtr = std::make_shared<Foo>();
{
std::lock_guard<std::mutex> lock<mutex>;
globalPtr = localPtr;
}
doSomething(localPtr);//读写localPtr,不是需要保护的globalPtr
}
2.3 shared_ptr的定制析构功能
shared_ptr的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数d,在析构对象时执行d(ptr),其中ptr是shared_ptr保存的对象指针。
template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );
//另一个,还能指定创建操作
template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );
2.4 enable_shared_from_this的作用
用enable_shared_from_this。这是一个以其派生类为模板类型实参的基类模板,继承它,this指针就能变身为shared_ptr。
2.5 示例代码
无论Stock和StockFactory哪个先被释放都不会影响运行:
#include <memory>
#include <string>
#include <mutex>
#include <functional>
#include <map>
using namespace std;
class Stock;
//StockFactory需要声明在栈上,不是堆上:
//share_ptr<StockFactory> factory(new StockFactory);
class StockFactory : public enable_shared_from_this<StockFactory>
{
public:
shared_ptr<Stock> get(const string &key)
{
shared_ptr<Stock> pStock;
lock_guard<mutex> lock(mutex_);
weak_ptr<Stock> &wkStock = stocks_[key]; //引用
pStock = wkStock.lock();
if (!pStock)
{
//设置析构回调函数,并转为弱回调,不会延长生命周期
pStock.reset(new Stock(key), bind(&StockFactory::weakDeleteCB, weak_ptr<StockFactory>(shared_from_this()), -1));
wkStock = pStock;
}
return pStock;
}
private:
static void weakDeleteCB(const weak_ptr<StockFactory> &wkFactory, Stock *stock)
{
shared_ptr<StockFactory> factory=wkFactory.lock());//升级weak指针
if (factory)
{
factory->removeStok(stock);
}
delete stock;
}
void removeStock(Stock *stock)
{
if (stock)
{
lock_guard<mutex> lock<mutex_>;
stocks_.erase(stock->key());
}
}
private:
mutable mutex mutex_;
std::map<string, weak_ptr<Stock>> stocks_;
};