在我的服务器模块中,有时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?
答案 0 :(得分: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
等,因此它比您从所发布的代码中获得的代码更简单。
答案 1 :(得分:0)
很难说,因为代码显然未定义
行为,但标准确实需要level
在调用get_p
之前初始化为空指针。并在
以确保准确初始化本地静态
曾经,编译器或多或少都要添加一个额外的标志;
类似的东西:
static test_dummy* _p = nullptr;
static bool isInitialized = false;
if ( !isInitialized ) {
_p = new test_dummy();
isInitialized = true;
}
(事实上,当然,上面显示的初始化是
零初始化,发生在其他任何事情之前。和
一个聪明的编译器可以实现显式初始化
第一次发生时不会导致_p
空指针,并使用_p
作为控制变量。)
以上不是线程安全的;为了使线程安全,
必须保护整个序列。 (还有更多或
不那么复杂的技巧,以避免需要一个完整的互斥,但是
在所有情况下,对isInitialized
的所有访问都必须是原子的。)
如果序列未受保护,则另一个线程看到的顺序
写入没有定义。所以一些线程正在看到
isInitialized
为true,但仍然看到空指针
_p
。