本文共 1231 字,大约阅读时间需要 4 分钟。
在日常开发中,许多程序员会在函数内使用静态变量,这样可以在函数内持久地记录某些信息,同时确保变量只能在函数内使用。不过,函数内静态的类对象初始化却存在一个线程安全问题,这个问题在我们的项目中导致了log4cxx封装接口的调用失败。
我们的项目封装了log4cxx的接口,调用了getWarn这个函数。由于getWarn函数内部使用了静态变量来初始化一个Level对象,导致程序在多线程环境下Crash。
为了深入分析这个问题,博主制作了一个简单例子,研究这个静态变量为何在多线程环境下不安全。这个例子用了一个TestObject类,通过TestFunction函数返回一个静态TestObject对象。
class TestObject {public: int m_iVal; TestObject() { m_iVal = 4; }};TestObject TestFunction() { static TestObject obj; return obj;} 这个代码在TestFunction函数中定义了一个静态对象obj,这个对象在TestFunction第一次被调用时会初始化,之后每次调用都会返回同一个obj。
初始化顺序:obj的构造函数只有在TestFunction第一次被调用时才会被调用,这意味着在多线程环境下,一个线程可能在另一个线程还未完成obj的初始化时,直接读取obj的成员变量,导致初始化的成员变量并没有被设置好。
竞态条件:如果两个线程同时进入TestFunction,第一个线程在完成obj的初始化后,可能移动到了一个与第二个线程竞争的位置,导致第二个线程误认为obj已经被初始化,而实际上obj还未完全构造好。
通过反汇编码可以看出,编译器在静态变量的初始化过程中引入了一种机制,比如通过一个标志位判断对象是否已被初始化。如果对象已经被初始化,直接返回它;否则,才会真正地初始化它,并调用构造函数。如果有多个线程同时进入这个过程,可能会出现竞态条件,使得对象的初始化结果不确定。
在C++11中,Visual Studio 2015引入了一种新的机制来确保静态对象的线程安全。你可能通过某些内置函数,比如 _Initialize_Thread_header 和 _Init_thread_footer 来实现这一点。这些函数在编译时默认是启用的,可以通过编译选项 /Zc:threadSafeInit= 来禁用。
这种优化不仅解决了静态变量的线程安全问题,还提升了代码的整体质量,使得代码更容易维护和扩展。
转载地址:http://xygoz.baihongyu.com/