本地静态指针变量不是线程安全的吗?

时间:2014-04-17 08:22:09

标签: c++ multithreading log4cxx

在我的服务器模块中,有时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?

2 个答案:

答案 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