本地静态指针变量不是线程安全的
Local static pointer variable is not thread safe?
在我的服务器模块上,有时log4cxx库会使它崩溃。
这是因为。。。
LevelPtr Level::getTrace() {
static LevelPtr level(new Level(Level::TRACE_INT, LOG4CXX_STR("TRACE"), 7));
return level;
}
static LevelPtr返回null ptr。
我测试了以下代码。
int start_flag = 0;
class test_dummy {
public:
int mi;
test_dummy() : mi(1)
{
std::cout << "hey!n";
}
static test_dummy* get_p()
{
static test_dummy* _p = new test_dummy();
return _p;
}
};
void thread_proc()
{
int i = 0;
while (start_flag == 0)
{
i++;
}
if (test_dummy::get_p() == 0)
{
std::cout << "error!!!n";
}
else
{
std::cout << "mi:" << test_dummy::get_p()->mi << "n";
}
}
void main()
{
boost::thread *pth_array[5] = {0,};
for (int i = 0; i < 5; i++)
{
pth_array[i] = new boost::thread(thread_proc);
}
start_flag = 1;
for (int i = 0; i < 5; i++)
{
pth_array[i]->join();
}
std::cin.ignore();
}
这确实是线程不安全的,但我很好奇为什么get_p()返回空指针而不是另一个分配的地址。
这是因为在执行new()操作时,值被设置为0吗?
编译器提供的这段代码中有一个竞争条件:
if (!level_initialized)
{
level_initialized = 1;
level = new Level(...);
}
return level;
在clang++3.5中,似乎有锁可以防止这种竞争,但如果不仔细查看编译器生成的代码,就不可能确切地说出发生了什么。但我怀疑这就是发生的事情。
以下是clang++3.5生成的(减去一些杂波)
_Z8getTracev: # @_Z8getTracev
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
cmpb $0, _ZGVZ8getTracevE5level # Guard variable
jne .LBB0_4
leaq _ZGVZ8getTracevE5level, %rdi
callq __cxa_guard_acquire
cmpl $0, %eax
je .LBB0_4
.Ltmp0:
movl $4, %eax
movl %eax, %edi
callq _Znwm # new
.Ltmp1:
movq %rax, -24(%rbp) # 8-byte Spill
jmp .LBB0_3
.LBB0_3: # %invoke.cont
leaq _ZGVZ8getTracevE5level, %rdi
movq -24(%rbp), %rax # 8-byte Reload
movq -24(%rbp), %rcx # 8-byte Reload
movl $0, (%rcx)
movq %rax, _ZZ8getTracevE5level
callq __cxa_guard_release
.LBB0_4: # %init.end
movq _ZZ8getTracevE5level, %rax
addq $32, %rsp
popq %rbp
retq
我修改了代码,将Level
用作int
,等等,所以它比您从发布的代码中获得的代码更简单。
很难说太多,因为代码显然没有定义行为,但标准确实要求level
在调用CCD_ 4之前初始化为空指针。在以确保本地静态被准确初始化一次,编译器或多或少地必须添加一个额外的标志;类似于:
static test_dummy* _p = nullptr;
static bool isInitialized = false;
if ( !isInitialized ) {
_p = new test_dummy();
isInitialized = true;
}
(当然,事实上,上面显示的初始化是零初始化,它发生在其他任何事情之前。和聪明的编译器可以实现显式初始化第一次发生到不能导致CCD_ 5空指针,并使用_p
作为控制变量。)
以上内容不安全;为了使其线程安全,必须保护整个序列。(还有更多或不那么复杂的技巧来避免对完全互斥的需要,但是在所有情况下,对isInitialized
的所有访问都必须是原子访问。)
如果序列不受保护,则另一个线程看到的顺序写入未定义。所以一些线程正在看到isInitialized
为true,但仍在中看到空指针_p
。
相关文章:
- 如何将元素添加到数组的线程安全函数?
- C++中的线程安全删除
- 在std::thread中,joinable()然后join()线程安全吗
- 在c++队列中使用pop和visit实现线程安全
- 以线程安全的方式调用"QQuickPaintedItem::updateImage(const QImage&image)"(no QThread)
- 全局变量 多读取器 一个写入器多线程安全?
- 共享队列的线程安全
- boost::文件系统::recursive_directory_iterator多线程安全
- 以线程安全的方式转换 C/C++ 中时区名称字符串的时区偏移量
- 线程安全运算符<<
- 如何使缓存线程安全
- C++线程安全:如果只有一个线程可以写入非原子变量,但多个线程从中读取. 会遇到问题吗?
- 提升精神 V2 Qi 语法线程安全吗?
- asio 链对象线程安全吗?
- 线程安全队列 c++
- 提供对不同类型的数据(建议、代码审查)的线程安全访问的类
- 如何以线程安全的方式更改目录?
- 线程安全的引用计数队列C++
- 析构函数和线程安全
- 适用于大型数组的无复制线程安全环形缓冲区