析构和多线程

鉴于C++没有(原生)垃圾回收机制,因此析构的race condition需要程序员来考虑

析构函数不需要锁来保证线程安全

析构函数不能用锁来保护,原因有:

1)基类与派生类的析构函数是割裂的,因此必然无法保证线程安全

2)安全的析构原则上只有其他线程都不用它时才是安全的,即成为“垃圾”时

这个结论较为显而易见

析构函数的race condition

析构函数有几个竞态条件:

1)析构的时候,是否有其他线程正在调用其成员函数

2)调用成员函数期间,其他线程是否正在析构其对象

3)在调用成员函数前,如何知道其是否存活

4)其他race condition

正如前面所说,如果有类似GC的机制就会好办很多

因此我们可以采取std::shared_ptr来解决1),2),而与std::weak_ptr组合成的弱回调(weak callback)可以解决3)

1)如果存在其他线程正在调用其成员函数,那么它的引用计数至少为1,不可能在析构

2)与1)同理,在调用成员函数期间,其他线程不可能正在析构其对象

3)通过std::weak_ptrlock()原子提升为std::shared_ptr,可以探明其是否存活

但请注意,在个别情况应用expired()来代替lock(),因为它引来了std::shared_ptr的拷贝

设想如果对象A与B存在相互引用,在A的析构中我想探明B是否有它的引用,如果用lock(),尽管可以保证其引用计数至少为2,但是如果突然另外一个线程将其析构,引用计数减1,待离开block scope时有得再次析构,这是很可能会陷入单线程重复加锁而陷入死锁

weak callback

1
2
3
4
5
6
7
8
// 伪代码
template<typename... Args>
void callback(std::weak_ptr<T> const& ptr, Args&&... args)
{
auto sp = ptr.lock();
if(sp)
sp->member-function(forward<Args>(args)...);
}

weak callback 的语义为:若对象存活, 则调用其成员函数

可以进一步用模板抽象为function object

参见:weak-callback.h

example

对象池