本系列为《你会不会处理多线程中的XXXX》 。 本系列参考资料:陈硕的《Linux服务端多线程编程》、还有我的经验。 适用人群:要有一定的C++基础、要会百度、要有一定的Linux服务器编程经验。 本文语言比较粗糙,应该不至于引起什么不适,大家都是成年人了。
看上面那张图,是不是能联想到多线程?
就那七个张伟,他们有一个共用属性,钱包里的钱。这天,张伟A在吃喝的时候,发现钱给没了,原因是张伟B拿去捐款了,那就很尴尬了。为了避免这种情况,怎么办?他们商量了一下,给钱包上个锁,是吧,谁要用谁上锁。上了锁谁都别用,用完再解锁,大家再用。
但是呢?今天张伟A在吃饭之前,看了下钱包,钱还够,但是总不能这会儿把钱包锁了吧,吃个饭那么久,别人都不要用了吗?所以他就没锁。就在这档口,张伟C买了个王者荣耀新出的皮肤,完了,我们可怜的张伟A要结账的时候,没钱了,又要刷盘子了。
所以说,这个锁啊,并不能百分百的就保证线程的安全。
像这种情况啊,那怎么办?那就在吃饭结账的时候看一眼有没有钱,没钱那就吃慢点,等着钱包的钱又有了再说。
这是操作系统的资源调度算法,拿来举个例子说线程安全。
本篇的主角,是对象与线程安全,
对象有什么线程安全的隐患?无非指针悬挂,内存泄漏;又或者多次释放,内存错乱。
参考博客:可重入函数对于线程安全的意义
对象构造要做到线程安全,就一点要求:不要暴露自己,即不要泄露this指针。
那就是做到以下几点:
对于第一点,如果非要回调函数才能构造,那就换二段式构造,先构造,在调用回调函数。
对于第三条,如果这个类是个基类呢?它构造完了并不是真的构造完了,还有子类等着呢。
之所以要这样设计(把this传给子类那另当别论),就是为了防止构造过程被打断,构造出一个半成品。
对象析构,在多线程里,由于竞态的存在,变得扑朔迷离。
看个例子:
Foo::~Foo(){
//拿锁
//析构
//解锁
}
void Foo::update(){
//拿锁
//数据操作
//解锁
}
extern Foo *f;//共享资源
A进程操作
delete f;
f = NULL;
B进程操作
if(f)
{
f->update();
}
那这就有一个很尴尬的情况了:
A在执行“析构”的时候,已经拿到了锁,而B通过了 f 的判断,因为那会儿指针还活着,然后被锁卡住了。
接下来会发生什么?不知道,因为对象析构的时候把锁也带走了。。。(锁属于对象,对象析构,锁也跑不了)
那怎么办?
别怕,参考博客:智能指针
一个动态创建的对象,是否还有效光看指针是看不出来的指针就是指向了一块内存而已,这块内存上的对象如果已经被销毁,那就根本不能访问。
shared_ptr是引用计数型智能指针,被纳入C11标准库。shared_ptr是一个类模板,它只有一个参数,使用起来很方便。
shared_str是强引用,只要有一个指向x对象的shared_ptr存在,该对象及不会被析构。
weak_ptr是弱引用,它不控制对象的生命周期,但是它知道对象是否还存在。如果对象存在,它可以升级成为shared_ptr。
讲这么多不如来个例子实在:
class Observer{
private:
std::vector<weak_ptr<Observer>> vwo; //像这样用啊
}
C++里面可能出现的内存问题大致有这么几个方面
对应解决: