线程安全的定义:
1、多个线程同时访问时,其表现出正确的行为
2、无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
3、调用端代码无须额外的同步或其他协调动作。(如在调用函数前后加锁等)
当析构函数遇到多线程:
C++程序员需要自己管理对象的生命期,这在多线程下显得尤为困难。这里讨论当多个线程同时可以看到一个对象时,对象销毁就有可能出现以下的情形:在即将析构一个对象时,需要知道此刻是否有其他线程在执行该对象的成员函数。
一个线程安全的Counter
class Counter: boost::noncopyable
{
public:
Counter() :valued(0){}
int value() const;
int getAndIncrease();
private:
int valued;
mutable MutexLock mutex;
};
int Counter::value()const
{
MutexLockGuard lock(mutex);
return valued;
}
int Counter::getAndIncrease()
{
MutexLockGuard lock(mutex);
int ret = valued++;
return ret;
}
这是一个线程安全的类,但如果动态创建该类的对象并通过指针来访问,那么多线程下对象析构的问题仍然存在,因为内部加的锁仅仅能够保证对象存在情况下执行不会出错,但对象本身总要析构,一析构,锁的作用就不复存在。因为锁本身就是数据成员。
对象构造的线程安全:
唯一的要求是在构造期间不要泄漏this指针,因为对象构造开始到结束要对对象进行修改只能通过this指针实现。即以下几点:
1、不要在构造函数中注册任何回调
2、也不要在构造函数中把this传给跨线程的对象
3、即使在构造函数的最后一行也不行(因为类可能会被继承,所以最后一行后可能还未开始构造派生类的部分,仍然不安全)
反例(使用观察者模式):
class Foo:public Observer
{
public:
Foo(Observable* s)
{
s->myregister(this);
}
virtual void upodate();
}
正确的方式:
class Foo public Observer
{
public:
Foo();
virtual void update();
void observer(Observerable* s)
{
s->myregister(this);
}
};
Foo* pFoo = new Foo;
Observable* s = getSubject();
pFoo->observe(s); //二段式构造,为了避免构造时泄漏this指针的无奈之举。
另外,不能同时读写一个class的两个对象,如果类中有锁,多线程下可能发生死锁。
void swap(Counter& a,Counter& b)
{
MutexLockGuard aLock(a.mutex);
MutexLockGuard bLock(b.mutex);
}
以上代码看似没问题,但特殊情况下,同时执行swap(a,b)和swap(b,a)就会死锁。所以多线程代码必须考虑到特殊的情况,就和单线程中的边界处理一样,作为一个程序员首要考虑的就是程序的健壮性。
如何解决我们的第一个问题(如何确定对象还活着,没有被析构)?
看似简单的解决方案:使用对象池来存储对象(换言之,只构造不析构,完事)。
说说对象池的缺点:
对象池的线程安全,应该都知道对象池中的对象用完后会放回对象池中(其实相当于并没有解决析构的本质问题,只是形式上是这样。)
全局共享数据引发的锁征用(惊群现象?)
如果共享对象的类型不止一种,使用模板?
最后,使用对象池的话,如果对象注册了任何非静态成员函数回调,那么必然在某处持有了this指针,这就同样会引发第一个问题。
一个典型的场景:
class Observer
{
public:
virtual ~Observer();
virtual void update() = 0;
};
class Observerable
{
public:
void myregister(Observer* x);
void unregister(Observer* x);
void notifyObservers(){
for(Observer* x:observer)
{
x->update();
}
}
private:
vector<Observer*> observer;
};
这里的问题是Observerable如何得知Observer是否还活着?不然x指针就无法使用,第一个想到的是Observer析构时进行解注册,但这里又回到了第一个问题,因为泄漏了this指针。
class Observer
{
public:
void observe(Observerable* s)
{
s->myregister(this);
subject = s;
}
virtual ~Observer()
{
subject->unregister(this);
}
virtual void update() = 0;
Observerable* subject;
};
这时候应该想到加锁来解决,但不是最好的解决办法,最好可以有一种方式来帮助我们管理对象,可惜指针和引用方式无法帮助我们提供对象是否存活。所以,使用智能指针是一种好办法,但也有需要注意的问题。