当一个对象能被多个线程同时看到时,那么对象的销毁时机就会变得模糊不清,可能出现多种竞态条件(对象析构和成员函数可能同时发生)。
C++标准库里的大多数class都不是线程安全的,包括std:: string、std::vector、std::map等,因为这些class通常需要在外部加锁才能供多个线程同时访问。

1 线程安全的对象创建

对象构造要做到线程安全,唯一的要求是在构造期间不要泄露**this**指针,因为构造期间对象没有完成初始化,半成品对象不能被其他线程访问(结果未知)。具体规则为:

  • 不要在构造函数中注册任何回调函数;
  • 不要在构造函数中把this传给跨线程的对象,便在构造函数的最后一行也不行
    1. //正确的示例:二段式的构造
    2. class Foo : public Observer //举例观察者模式
    3. {
    4. public:
    5. Foo();//干净的构造函数,不返回或赋值this指针
    6. void observe(Observer* s)
    7. {
    8. s->register(this);//这里已构造完,可以赋值
    9. }
    10. };
    11. //使用
    12. Foo* foo = new Foo();
    13. Observer s = getSubject();
    14. 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保护。

  1. void read()
  2. {
  3. std::shared_ptr<Foo> localPtr;
  4. {//使用临界区,读写在临界区外,缩短了锁的粒度
  5. std::lock_guard<std::mutex> lock<mutex>;
  6. localPtr = globalPtr;
  7. }
  8. doSomething(localPtr);//读写localPtr,不是需要保护的globalPtr
  9. }
  10. void write()
  11. {
  12. std::shared_ptr<Foo> localPtr = std::make_shared<Foo>();
  13. {
  14. std::lock_guard<std::mutex> lock<mutex>;
  15. globalPtr = localPtr;
  16. }
  17. doSomething(localPtr);//读写localPtr,不是需要保护的globalPtr
  18. }

2.3 shared_ptr的定制析构功能

shared_ptr的构造函数可以有一个额外的模板类型参数,传入一个函数指针或仿函数d,在析构对象时执行d(ptr),其中ptr是shared_ptr保存的对象指针。

  1. template< class Y, class Deleter >
  2. shared_ptr( Y* ptr, Deleter d );
  3. //另一个,还能指定创建操作
  4. template< class Y, class Deleter, class Alloc >
  5. 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哪个先被释放都不会影响运行:

  1. #include <memory>
  2. #include <string>
  3. #include <mutex>
  4. #include <functional>
  5. #include <map>
  6. using namespace std;
  7. class Stock;
  8. //StockFactory需要声明在栈上,不是堆上:
  9. //share_ptr<StockFactory> factory(new StockFactory);
  10. class StockFactory : public enable_shared_from_this<StockFactory>
  11. {
  12. public:
  13. shared_ptr<Stock> get(const string &key)
  14. {
  15. shared_ptr<Stock> pStock;
  16. lock_guard<mutex> lock(mutex_);
  17. weak_ptr<Stock> &wkStock = stocks_[key]; //引用
  18. pStock = wkStock.lock();
  19. if (!pStock)
  20. {
  21. //设置析构回调函数,并转为弱回调,不会延长生命周期
  22. pStock.reset(new Stock(key), bind(&StockFactory::weakDeleteCB, weak_ptr<StockFactory>(shared_from_this()), -1));
  23. wkStock = pStock;
  24. }
  25. return pStock;
  26. }
  27. private:
  28. static void weakDeleteCB(const weak_ptr<StockFactory> &wkFactory, Stock *stock)
  29. {
  30. shared_ptr<StockFactory> factory=wkFactory.lock());//升级weak指针
  31. if (factory)
  32. {
  33. factory->removeStok(stock);
  34. }
  35. delete stock;
  36. }
  37. void removeStock(Stock *stock)
  38. {
  39. if (stock)
  40. {
  41. lock_guard<mutex> lock<mutex_>;
  42. stocks_.erase(stock->key());
  43. }
  44. }
  45. private:
  46. mutable mutex mutex_;
  47. std::map<string, weak_ptr<Stock>> stocks_;
  48. };