您的位置:首页 > 其它

线程安全的对象生命周期管理

2017-11-04 18:12 260 查看
    首先有关线程安全的定义:

1 多线程同时访问保证其逻辑正确性

2 在多线程同时运行时,保证线程执行顺序的正确性

3 调用该部分代码时,不需要额外的同步操作,从这些方面可以判断出标准库中如vector,list,map等都是非线程安全的。

    使用同步原语保证类的内部状态实现类的线程安全并不是难事,但是对象的创建和销毁不能由对象自身的锁来保证,如何避免在对象析构时可能的死锁呢?首先在对象析构可能出现竞争情景:

1 在对象即将析构时,如何确定当前没有其他线程引用该对象?

2 如何保证在执行成员函数期间,对象不会被其他线程析构掉?

3 在调用每个对象的成员函数前,如何得知对象还活着?会不会它的析构函数正好执行到一半?

    对象的构造要做到线程安全,我们唯一要做的就是不要在构造未完成之前泄露this指针,即不要在构造函数中注册任何回调函数,也不要再构造函数中把this指针传递给其他线程的函数,对象,即便即便在构造函数最后一行也不行。试想一下如果我们把一个未构造完成的半成品对象泄露给其他对象,其他线程会发生什么糟糕的事,不光导致构造失败,同样会导致获得这个对象的线程的这个逻辑的完全的混乱。对此最好的解决办法就是采用构造函数+initialize()的方法,虽然很麻烦但是多线程的情况下也没有办法,处置之外还有一个好处就是,简化构造函数的异常错误处理,通过initialize函数的返回值判断构造是否成功。

    还有一点对于对象的销毁在多线程中是一件非常棘手的事情,对象的析构造单线程中没有任何的问题,但是在多线程中有很多竞争条件存在,对于一般成员函数而言我们只要通过共享变量就可以保证成员中的临界区的线程安全,但是这里同样与一个问题,析构函数中我们会销毁共享变量,可是构造函数中我们又必须保证共享变量的有效性。                             


             

    尽管线程A在销毁对象之后把指针置为了null,并且线程B在调用update()函数之前检查了指针是否为NULL,但是还是可能会出现错误,线程A执行到了1处,并且获得了共享变量,继续往下执行,在此之前线程b调用update函数并且通过了检测阻塞在2处,接下来会放生我们最不愿看到的事,线程a析构函数释放了共享变量,共享变量无效线程b获取不到,永远阻塞在这,就算获取了共享变量继续执行,也会由于x已经析构造成程序core dump,在这里前辈们“变量delete后置为null的经验”毫无作用。

   因此我们可以看到,作为class数据成员MutexLock只能同步本class的其他数据成员的读写,他不能保证对象的安全析构,因为他的生命周期比对象的生命周期短,对于基类对象,当调到基类析构函数的时候,派生类的对象的部分已经析构了,因此我们要了解基类对象拥有的mutex并不能保证整个析构过程,当然析构函数一般情况下时不需要线程安全的,除非我们在别的线程中要访问该对象。还有一点要注意如果我们同时读写同一class的两个对象,有潜在的死锁可能。比如:

void swap(Count& a,Count& b)
{
Lock aLock(a.mutex);
Lock bLock(b.mutex);
......
}
  如果线程a执行swap(a,b),而同时线程b执行swap(b,a),a锁住了a,而b线程锁住了b,此时就会死锁。

     最后我们说到指针,如果我们的原始指针指向的对象是非法的,我们又把它传递给了别的线程,此时我们不能判断指针的有效性,这样将会造成很严重的后果。当然对于这个问题我们可以利用c++11的只能指针来解决。但是我们也要意识到智能指针也有他自己的缺点:

1 shared_ptr是强引用,如果我们不小心遗留了一个拷贝,则该对象永远不会释放,同样会造成内存泄露。

2 当shared_ptr做函数参数时因为要修改引用计数,而且修改引用计数时还要加锁,因此他的拷贝开销要比原始指针要高(通常我们可以以const reference的方式来传递,但是也不是什么情况都可以用这种方式来传递的)

   

                                                                                                       
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: