Helgrind 报告了使用 Singleton 及其构造函数之间可能存在的竞争
helgrind reports possible race between use of singleton and its constructor
如前所述,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 %dn", 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 之间可能存在的数据竞争,这设置了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 保证不会仍在运行的变量。
否则,第一次初始化这样的变量 控制通过其声明;考虑这样的变量 初始化完成后初始化。[...]如果控件同时输入声明,而 变量正在初始化,并发执行应等待 以完成初始化。[...]
我错了吗?是赫尔格林德缺陷吗?是否已知此用例会从裂缝中溜走,以便赫尔格林德报告可能的比赛?
反汇编函数线程 1,我看到对__cxa_guard_acquire的调用 和__cxa_guard_release,我认为我们可以合理地假设是什么 保护构造函数。但是,此类调用不会被拦截 赫尔格林德,因此,赫尔格林德没有观察到任何同步。这是Valgrind/helgrind中的一个错误/弱点,值得在valgrind bugzilla上提交一个错误。但请注意,快速读取代码,对 __cxa_guard_acquire 和 __cxa_guard_release 的调用似乎与一对锁定/解锁并不直接匹配:看起来代码可能只调用 acquire,然后不调用释放:
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 工具同样存在相同的错误/弱点。
我认为这是一个地狱般的缺陷。该标准保证静态初始化在稍后读取之前进行排序,并且证据(见下文)表明,不仅决定是否运行构造函数,而且实际上整个构造函数都在锁后面。
修改示例,使构造函数读取
key_type() : value(0) {
sleep (1);
pthread_yield();
value = 42;
}
输出 42 两次。这表明在测试(如有必要)和从初始化开始时获取的锁在构造函数完成之前不会释放。
除了现有的答案之外,如果您查看生成的完整程序集,您会发现在锁定之前对保护变量进行了额外的检查。 对此(锁定下)的写入与对此的读取(锁定外的"预览")可能被视为数据竞赛。
看这里: https://godbolt.org/z/3bf66qr4G
getInstance():
pushq %rbp
movq %rsp, %rbp
# this is a "preview", probably seen as a data race:
movzbl guard variable for getInstance()::inst(%rip), %eax
testb %al, %al
sete %al
testb %al, %al
je .L4
# preview says "does not exist"
# check again whether instance exists, under lock
movl $guard variable for getInstance()::inst, %edi
call __cxa_guard_acquire
testl %eax, %eax
setne %al
testb %al, %al
je .L4
# instance does not exist - create one
movl $_ZZ11getInstancevE4inst, %edi
call Foo::Foo() [complete object constructor]
movl $__dso_handle, %edx
movl $_ZZ11getInstancevE4inst, %esi
movl $_ZN3FooD1Ev, %edi
call __cxa_atexit
movl $guard variable for getInstance()::inst, %edi
call __cxa_guard_release
.L4:
movl $_ZZ11getInstancevE4inst, %eax
popq %rbp
ret
- C++LinkedList问题.数据类型之间存在冲突?没有匹配的构造函数
- 为什么C++容器之间存在比较运算符
- 为什么返回前的值和返回后的值之间存在差异?
- 为什么堆栈中的函数局部变量之间存在内存空间
- 相对于一元算术运算符+,C和C++之间存在差异的原因是什么
- 函数返回类型和名称之间存在未知关键字
- 我找不到程序中的歧义,但编译器说 check(int) 和 check(float) 之间存在歧义
- 引用的初始化无效:指针和值之间存在差异
- VS2005调试模式和发布模式之间存在巨大的性能影响
- 为什么在Mac OS X上使用size_t时uint32_t和uint64_t之间存在歧义
- 如何提取两个括号之间存在的字符串
- 来自 vtable 的未定义符号是否意味着接口和实现之间存在错误
- 实时时间和用户时间之间存在巨大差异
- 在程序继续执行的同时,如何使代码的两部分之间存在时间间隔或延迟?C++
- g++输出和Visual Studio输出之间存在差异.浮点变量
- 为什么我的静态方法的返回值与定义的构造函数(在 c++ 中)之间存在类型不匹配?
- 复制构造函数和转发构造函数之间存在冲突
- libc++和libstdc++之间存在差异
- 指针上的interpret_cast在char和unsigned char之间存在缺陷
- 为什么在多重定义的情况下char*和char[]之间存在差异