there所述,Meyer的单例在C ++ 11中是线程安全的。

因此,我希望这段代码可以:

#include <stdio.h>
#include <pthread.h>

struct key_type {
    int value;
    key_type() : value(0) { }
};


void * thread1(void*) {
    static key_type local_key;
    printf("thread has key %d\n", local_key.value);
    return NULL;
}

int main()
{
    pthread_t t[2];
    pthread_create(&t[0], NULL, thread1, NULL);
    pthread_create(&t[1], NULL, thread1, NULL);
    pthread_join(t[0], NULL);
    pthread_join(t[1], NULL);
}


(代码故意过分简化,我知道我可以轻易地进行零初始化。)

我正在使用g ++-7.1.0进行编译。 Helgrind(valgrind-3.12.0)报告在读取local_key.value和设置value的ctor之间可能发生数据竞争。

==29036== Possible data race during read of size 4 at 0x601058 by thread #3
==29036== Locks held: none
==29036==    at 0x4006EA: thread1(void*) (datarace-simplest.cpp:12)
==29036==    by 0x4C32D06: mythread_wrapper (hg_intercepts.c:389)
==29036==    by 0x4E45493: start_thread (pthread_create.c:333)
==29036==    by 0x59DEAFE: clone (clone.S:97)
==29036==
==29036== This conflicts with a previous write of size 4 by thread #2
==29036== Locks held: none
==29036==    at 0x400780: key_type::key_type() (datarace-simplest.cpp:6)
==29036==    by 0x4006DF: thread1(void*) (datarace-simplest.cpp:11)
==29036==    by 0x4C32D06: mythread_wrapper (hg_intercepts.c:389)
==29036==    by 0x4E45493: start_thread (pthread_create.c:333)
==29036==    by 0x59DEAFE: clone (clone.S:97)
==29036==  Address 0x601058 is 0 bytes inside data symbol "_ZZ7thread1PvE9local_key"


我以为c ++ 11标准(第6.7节)保证了local_key会被一劳永逸地初始化,以便进一步访问处理的ctor保证不会继续运行的变量。


  否则,将在第一次初始化此类变量
  控制通过其声明;这样的变量被认为
  在初始化完成后进行初始化。 [...]如果控件同时输入声明
  变量正在初始化,并发执行应等待
  完成初始化。 [...]


我错了吗 ?这是黑面缺陷吗?是否知道此用例可以穿越裂缝,以便Helgrind报告可能的种族?

最佳答案

分解函数thread1,我看到对__cxa_guard_acquire的调用
和__cxa_guard_release,我认为我们可以合理地假设是
保护构造函数。但是,此类呼叫不会被拦截
Helgrind认为,因此Helgrind不会进行任何同步。这是Valgrind / helgrind中的错误/弱点,值得在valgrind bugzilla上提交错误。但是请注意,快速阅读代码后,对__cxa_guard_acquire和__cxa_guard_release的调用似乎并不直接匹配一对锁定/解锁:看起来该代码可能只是调用了acquire,然后未调用release:

   00x000000000040077e <+24>:   mov    $0x600d00,%edi
   0x0000000000400783 <+29>:    callq  0x400610 <__cxa_guard_acquire@plt>
   0x0000000000400788 <+34>:    test   %eax,%eax
   0x000000000040078a <+36>:    setne  %al
   0x000000000040078d <+39>:    test   %al,%al
   0x000000000040078f <+41>:    je     0x4007a5 <thread1(void*)+63>
   0x0000000000400791 <+43>:    mov    $0x600d08,%edi
   0x0000000000400796 <+48>:    callq  0x40082e <key_type::key_type()>
   0x000000000040079b <+53>:    mov    $0x600d00,%edi
   0x00000000004007a0 <+58>:    callq  0x400650 <__cxa_guard_release@plt>
   0x00000000004007a5 <+63>:    mov    0x20055d(%rip),%eax        # 0x600d08 <_ZZ7thread1PvE9local_key>


稍微调试之后,看似警卫队位于
local_key,并在构造对象后设置为1。
我不太清楚__cxa_guard_release必须做什么。我想需要更多阅读c ++运行时库代码,以了解如何(也许)被告知helgrind关于在那里发生的事情。

还要注意,valgrind drd工具同样遭受相同的错误/弱点。

07-24 14:05