对象的创建

对象构造做到线程安全,唯一的要求是不在构造期间泄漏this指针
1.不在构造函数中注册任何回调。只要有回调必有指向this的指针
2.不在构造函数中把this交给跨线程的对象
3.即使在构造函数最后一行也不行。因为这个类可能是个基类,其子类还未构造完成
注:在构造未完成之前将this交给其他对象或者线程,会造成难以预料的后果。可以使用二段式构造,在第二段构造时将this传出,完成回调函数的注册工作。

对象的析构

析构函数在单线程中只需要注意避免空悬指针和野指针,在多线程中存在很多竟态条件。
在类中的其他函数可以使用自己的互斥器变量保护线程安全,但是在析构函数中,mutex是要被销毁的,因为析构动作发生在对象销毁之后。对象的析构本身不需要保护,只有大家都访问不到时才会析构,不然还是会产生竟态条件。
图片说明
注:当一个函数访问一个class的两个对象时可能会存在死锁,一定要保证锁的顺序,例如swap(a,b)与swap(b,a),如果按照形参的顺序加锁就会导致死锁,一定按照锁的地址顺序取加锁。

使用智能指针

使用指向对象的原始指针可能会造成空悬指针,解决的方法就是在指针与原始对象之间加一个中间对象,中间对象具有计数,每少一个指针引用计数就减一,变为0时则释放内存,这其实也就是智能指针的思想。
智能指针的引用计数是原子操作,没有锁,性能很好,
图片说明

Observer模式

通过weak_ptr可以探查对象的生死来解决在observable中的遍历observer们时存在的竟态条件(因为不知道观察者们是否已经释放)。将vector<observer *>替换成vector<std::weak_ptr<observer>>。
观察者模式存在的问题:
图片说明 </observer>

控制锁的粒度最小

图片说明

shared_ptr存在的技术陷阱

意外延长对象的生命周期

如果不小心遗留一个shared_ptr的拷贝,那么对象将一直存在。比如std::bind函数,会把实参拷贝一边,如果含有shared_ptr那么对象的生命周期将不短于std::function。

函数参数

因为要修改引用参数(而且拷贝时通常需要加锁),shared_ptr拷贝成本比原始指针成本要高,但是需要拷贝的时候并不多,大多情况下可以用const reference方式传递。一个线程只需要最外层函数有一个实体对象,之后都以const reference传递即可。按照这个原则就不会因为反复拷贝智能指针导致性能问题,并且对象是在线程的栈上,不会被其他线程看到,所以时安全的。

图片说明

图片说明
图片说明
图片说明
图片说明