2

在我的服务器模块中,有时 log4cxx 库会导致它崩溃。

这是因为 ...

LevelPtr Level::getTrace() {
   static LevelPtr level(new Level(Level::TRACE_INT, LOG4CXX_STR("TRACE"), 7));
   return level;
}

静态 LevelPtr 返回空指针。

我测试了以下代码。

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?

4

2 回答 2

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等,因此它比您从您发布的代码中获得的代码更简单。

于 2014-04-17T08:39:39.840 回答
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是真的,但仍然在 _p.

于 2014-04-17T09:29:30.353 回答