为何函数级静态变量在AIX与Solaris中不具备线程安全性?
先来看下你写的函数代码:
bool foo(const std::string& str) { static const std::string ky[] = { "KeyWord1", "KeyWord2", "KeyWord3" }; static const std::set<std::string> kySet(ky, ky+ sizeof(ky)/sizeof(ky[0])); return kySet.find(str) != kySet.end(); }
你想用static保证变量只有一份副本的思路没问题,但问题出在静态局部变量的初始化线程安全性上,不同平台的编译器实现差异导致了这个区别:
Linux平台安全的原因:
Linux上常用的GCC编译器从4.3版本开始,就实现了C++11标准里的「Magic Static」特性——当多个线程第一次进入函数时,静态局部变量的初始化会被自动保护:只有一个线程会执行初始化逻辑,其他线程会阻塞等待,直到初始化完成。所以你的ky数组和kySet只会被初始化一次,不会出现竞态条件,自然是线程安全的。AIX和Solaris出问题的原因:
这两个平台的默认编译器(比如AIX的xlC、Solaris的Sun Studio)在较早版本里,并没有完全支持C11的这个线程安全初始化特性。在C11标准出台前,C++语言本身没有规定静态局部变量的初始化是线程安全的——这意味着如果多个线程同时第一次调用foo()函数,它们可能会同时触发静态变量的初始化:比如多个线程同时构造kySet,或者某个线程还在初始化数组时,另一个线程已经用不完整的数组去构造集合,最终导致集合数据错误、程序行为异常甚至崩溃。
跨平台线程安全的解决方案
给你几个可行的处理方案:
提前初始化(推荐):
在程序启动的单线程阶段(比如main()函数刚开始时),就初始化好这个关键词集合,把它放在全局作用域或者命名空间里,这样foo()函数直接使用已经初始化完成的变量,完全避免初始化阶段的线程竞争。手动加锁保护初始化:
自己在函数里加一个互斥锁,确保只有一个线程能执行静态变量的初始化逻辑。比如用POSIX的pthread_mutex_t(如果代码兼容POSIX环境):bool foo(const std::string& str) { static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; static std::set<std::string>* kySet = nullptr; pthread_mutex_lock(&init_mutex); if (!kySet) { static const std::string ky[] = { "KeyWord1", "KeyWord2", "KeyWord3" }; kySet = new std::set<std::string>(ky, ky + sizeof(ky)/sizeof(ky[0])); } pthread_mutex_unlock(&init_mutex); return kySet->find(str) != kySet->end(); }注意锁的初始化和内存管理,不过对于这类静态集合,少量内存泄漏基本不影响程序运行。
升级编译器版本:
如果条件允许,把AIX/Solaris上的编译器升级到支持C++11及以后标准的版本,这样就能像Linux一样,利用标准自带的线程安全初始化特性,不用改代码就能解决问题。
内容的提问来源于stack exchange,提问作者Yong Li

