如上所述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
和ctor之间报告可能的数据竞赛,ctor设置value
。
==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缺陷吗?这个用例是否已知漏洞,以便helgrind报告可能的种族?
答案 0 :(得分:0)
我认为这是一个神奇的缺陷。该标准保证在稍后读取之前对静态初始化进行排序,并且证据(见下文)表明不仅决定是否运行构造函数,而且实际上整个构造函数都在锁定之后。
修改示例以使构造函数读取
key_type() : value(0) {
sleep (1);
pthread_yield();
value = 42;
}
outputs 42两次。这表明在构造函数完成之前,在必要时进行测试并从初始化开始获取的锁定不会被释放。
答案 1 :(得分:0)
反汇编函数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工具同样会遇到相同的bug /弱点。