对象在销毁时,出现的竞态条件:
C++标准库的多数类不符合线程安全,比如vector、string等等。
对象构造时从以下2点考虑到线程安全:
注册回调的方式应该是二段式构造:构造函数+initalize(),如下:
class Foo : public Observer
{
public:
Foo();
void observe(Observer* s)
{
s->register_(this);
}
};
Foo* pFoo = new Foo;
Obervable* s = getSubject();
pFoo->observe(s);//二段式构造
空悬指针(dangling pointer):指向已经销毁的对象。
野指针(wild pointer):未经初始化的指针。
对于普通的成员函数,只需要使用mutex保证临界区不会重叠,就是线程安全的。但是mutex本身不是一直有效,析构会把mutex销毁!对于以下两个函数,使用mutex保护析构:
Foo::~Foo()
{
Lockguard m(mutex_);
//...
}
Foo::update()
{
Lockguard m(mutex_);
//...
}
//thread A
delete x;
x = nullptr;
//thread B
if (x)
{
x->update();
}
以上代码,如果线程A首先执行,线程B阻塞在update调用之前,那么将x赋值为空指针的做法,完全无法避免竞争,线程B触发core dump。以上例子,说明对象销毁之后,把指针置为nullptr完全没用。作为数据成员的mutex不能保护析构。对于基类来说,在进入基类的析构函数之前,派生类的析构已经调用了,说明mutex也无法保护整个析构过程。
此外,mutex可能导致死锁,参考以下代码:
void swap(Foo& a, Foo& b)
{
Lockguard m1(a.mutex_);
Lockguard m2(b.mutex_);
//...
}
//thread A
swap(a, b);
//thread B
swap(b, a);
总结:一个函数要锁住多个相同类型的对象,必须始终按照相同的顺序加锁,实际做法可以按照地址大小顺序加锁。
动态创建的对象是否还活着,看指针和引用是看不出来的。面向对象的程序中,对象之间的关系有3种:
以观察者模式举例
//观察者
class Observer
{
public:
void update();
void ob(Observable* s)
{
s->register_(this);
subject_ = s;
}
~Oberver()
{
subject_->unregister(this);
}
Observable* subject_;
}
//被观察的
class Observable
{
public:
void register(Observer* s);
void unregister(Observer* s);
void notify()
{
for (Observer* x : observers_)
{
x->update();
}
}
vector observers_;
}
以上例子中,被观察者在notify时,如何确保每一个指针是可用的?观察者在析构时unregister,也有可能和notify发生竞争。
原始指针是坏的,如果有一种机制能够判断指针指向的对象是否存活,并且判断有多少地方在使用这个对象,就能安全的使用并销毁对象。
shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。
std::shared_ptr sp1(new int(22)); //新建
std::shared_ptr sp2 = sp1;//拷贝
std::cout << "cout: " << sp2.use_count() << std::endl; // 打印引用计数
sp1.reset(); // 让引用计数减一
weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但可以使用lock获得一个可用的shared_ptr对象
weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。
std::shared_ptr sp1(new int(22));
std::shared_ptr sp2 = sp1;
std::weak_ptr wp = sp1; // 指向shared_ptr所指对象
std::cout << "count: " << wp.use_count() << std::endl; // count: 2
sp1.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 1
sp2.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 0, pointer is invalid
std::shared_ptr sp = wp.lock(); // 转换为shared_ptr
if (sp != nullptr)
{
std::cout << "still: " << *sp << std::endl;
}
else
{
std::cout << "still: " << "pointer is invalid" << std::endl;
}
unique_ptr,是用于取代c++98的auto_ptr的产物,在c++98的时候还没有移动语义(move semantics)的支持,有了移动语义,使用move()把unique_ptr传入函数,这样你就知道原先的unique_ptr已经失效了.移动语义本身就说明了这样的问题。
unique_ptr 是一个独享所有权的智能指针,它提供了严格意义上的所有权,包括:
unique_ptr 可以实现如下功能:
unique_ptr fun()
{
return unique_ptr(new Test("789"));
}
int main()
{
unique_ptr ptest(new Test("123"));
unique_ptr ptest2(new Test("456"));
ptest->print();
ptest2 = std::move(ptest);//不能直接ptest2 = ptest
if(ptest == NULL) //直接==和get()居然都可以。。。
cout<<"ptest = NULL\n";
Test* p = ptest2.release();
p->print();
ptest.reset(p);
ptest->print();
ptest2 = fun(); //这里可以用=,因为使用了移动构造函数
ptest2->print();
return 0;
}
作者陈硕说他的C++程序从来没有出过内存问题。
C++的内存问题有如下几个方面,智能指针可以解决前5个。
作者认为,现代C++程序不应该出现delete语句,所有资源通过容器或者智能指针管理。
修改:使用weak_ptr实现Observer,即在Observable中使用weak_ptr探测Observer对象的生死。
class Observable
{
public:
void register(weak_ptr x);
void unregister(weak_ptr x);
void notify()
{
lock_guard lg(mutex_);
for (auto it = observers_.begin(); it != observers_.end(); )
{
shared_ptr pObserver(it->lock());
if (pObserver)
{
pObserver->update();
++it;
}
else
{
it = observers_.erase(it);
}
}
}
private:
list > observers_;
mutable mutex mutex_;
};
class Observer
{
public:
void update();
void ob(weak_ptr x)
{
shared_ptr pObservable(x.lock());
if (pObservable)
{
subject_ = pObservable;
}
}
~Observer()
{
shared_ptr pObservable(subject_.lock());
if (pObservable)
{
shared_ptr x(this);
pObservable->unregister(shared_ptr x(this));
}
}
private:
weak_ptr subject_;
};
以上代码存在的问题:
shared_ptr的引用计数是原子的,但是shared_ptr并非线程安全的,多个线程读写时需要加锁。
//globalPtr全局shared_ptr变量
void read()
{
shared_ptr localPtr;
{
lock_guard lg(mutex_);
localPtr = globalPtr;
}
// use localPtr
}
void write()
{
shared_ptr newPtr(new Foo);
{
lock_guard lg(mutex_);
globalPtr = newPtr;
}
// use newPtr
}
shared_ptr是强引用,只要还有shared_ptr存活,对象就不会析构。例如:
vector > x;
//bind会拷贝实参,使得对象生命期不短于std::function
std::bind();
Stock是股票类,实现根据股票名字key,返回股票ID的对象池StockFactory。
class StockFactory
{
public:
shared_ptr get(const string& key)
{
shared_ptr pStock;
lock_guard lg(mutex_);
weak_ptr& wStock = stocks_[key];
pStock = wStock.lock();
if (!pStock)
{
pStock.reset(new Stock(key));
wStock = pStock;
}
return pStock;
}
private:
mutable mutex mutex_;
map> stocks_;
};
以上代码中,map结构只增不减,解决办法:shared_ptr调用析构时,可以定制析构,即传入一个bind,析构时调用。
class Stock
{
public:
string key;
string id;
Stock(string k) : key(k) {};
~Stock()
{
cout << "~Stock called" << endl;
}
};
class StockFactory
{
public:
boost::shared_ptr get(const string& key)
{
boost::shared_ptr pStock;
lock_guard lg(mutex_);
boost::weak_ptr& wStock = stocks_[key];
pStock = wStock.lock();
if (!pStock)
{
//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, this, _1));
wStock = pStock;
}
return pStock;
}
private:
void deleteStock(Stock* s)
{
if (s)
{
lock_guard lg(mutex_);
stocks_.erase(s->key);
delete s;
}
cout << "deleteStock called" << endl;
}
mutable std::mutex mutex_;
map> stocks_;
};
上面代码设置shared_ptr的deleter时,使用了裸指针。如果执行deleter时的StcokFactory已经死亡,就会发生错误。使用enable_shared_from_this可以在成员函数中得到shared_ptr,要求实例必须在堆上。
boost::shared_ptr get(const string& key)
{
boost::shared_ptr pStock;
lock_guard lg(mutex_);
boost::weak_ptr& wStock = stocks_[key];
pStock = wStock.lock();
if (!pStock)
{
//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
//pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, this, _1));
pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, enable_shared_from_this(), _1));
wStock = pStock;
}
return pStock;
}
上面代码延长了StockFactory的生命期,只要不发生回调,就不会销毁。使用weak_ptr产生弱回调效果。
boost::shared_ptr get(const string& key)
{
boost::shared_ptr pStock;
lock_guard lg(mutex_);
boost::weak_ptr& wStock = stocks_[key];
pStock = wStock.lock();
if (!pStock)
{
//bind绑定非静态成员函数时,要传入函数的隐含的第一个参数this
pStock.reset(pTemp, boost::bind(&StockFactory::deleteStock, boost::weak_ptr(shared_from_this()), _1));
wStock = pStock;
}
return pStock;
}
void deleteStock(const boost::weak_ptr& sf, Stock* s)
{
shared_ptr f(sf.lock());
if (f && s)
{
lock_guard lg(mutex_);
stocks_.erase(s->key);
delete s;
}
cout << "deleteStock called" << endl;
}
经典的弱回调实现
template
class WeakCallback
{
public:
WeakCallback(const std::weak_ptr& object,
const std::function& function)
: object_(object), function_(function)
{
}
// Default dtor, copy ctor and assignment are okay
void operator()(ARGS&&... args) const
{
std::shared_ptr ptr(object_.lock());
if (ptr)
{
function_(ptr.get(), std::forward(args)...);
}
}
private:
std::weak_ptr object_;
std::function function_;
};