helgrind 报告了使用单例和它的构造函数之间可能存在的竞争
helgrind reports possible race between use of singleton and its constructor
如前所述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 保证不会保持 运行.
Otherwise such a variable is initialized the first time
control passes through its declaration; such a variable is considered
initialized upon the completion of its initialization. [...] If control enters the declaration concurrently while
the variable is being initialized, the concurrent execution shall wait
for completion of the initialization. [...]
我错了吗?这是一个 helgrind 缺陷吗?这个用例是否已知会从裂缝中溜走,以至于 helgrind 报告 可能 竞争?
我认为这是一个严重的缺陷。该标准保证静态初始化在稍后读取之前排序,并且证据(见下文)表明不仅决定是否 运行 构造函数,而且实际上整个构造函数都在锁后面。
修改您的示例,使构造函数读取
key_type() : value(0) {
sleep (1);
pthread_yield();
value = 42;
}
outputs 42 两次。这表明在必要时进行测试并从初始化开始时获得的锁在构造函数完成之前不会被释放。
反汇编函数 thread1,我看到对 __cxa_guard_acquire 的调用
和 __cxa_guard_release,我认为我们可以合理地假设是什么
保护构造函数。但是,此类呼叫不会被拦截
由 helgrind,因此,helgrind 没有观察到任何同步。这是 Valgrind/helgrind 中的 bug/weakness,值得在 valgrind bugzilla 上提交错误。但是请注意,快速阅读代码,对 __cxa_guard_acquire 和 __cxa_guard_release 的调用似乎不直接匹配一对 lock/unlock:看起来代码可能只是调用 acquire,然后不调用发布:
00x000000000040077e <+24>: mov [=10=]x600d00,%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 [=10=]x600d08,%edi
0x0000000000400796 <+48>: callq 0x40082e <key_type::key_type()>
0x000000000040079b <+53>: mov [=10=]x600d00,%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/weakness。
如前所述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
.
==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 保证不会保持 运行.
Otherwise such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. [...] If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. [...]
我错了吗?这是一个 helgrind 缺陷吗?这个用例是否已知会从裂缝中溜走,以至于 helgrind 报告 可能 竞争?
我认为这是一个严重的缺陷。该标准保证静态初始化在稍后读取之前排序,并且证据(见下文)表明不仅决定是否 运行 构造函数,而且实际上整个构造函数都在锁后面。
修改您的示例,使构造函数读取
key_type() : value(0) {
sleep (1);
pthread_yield();
value = 42;
}
outputs 42 两次。这表明在必要时进行测试并从初始化开始时获得的锁在构造函数完成之前不会被释放。
反汇编函数 thread1,我看到对 __cxa_guard_acquire 的调用 和 __cxa_guard_release,我认为我们可以合理地假设是什么 保护构造函数。但是,此类呼叫不会被拦截 由 helgrind,因此,helgrind 没有观察到任何同步。这是 Valgrind/helgrind 中的 bug/weakness,值得在 valgrind bugzilla 上提交错误。但是请注意,快速阅读代码,对 __cxa_guard_acquire 和 __cxa_guard_release 的调用似乎不直接匹配一对 lock/unlock:看起来代码可能只是调用 acquire,然后不调用发布:
00x000000000040077e <+24>: mov [=10=]x600d00,%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 [=10=]x600d08,%edi
0x0000000000400796 <+48>: callq 0x40082e <key_type::key_type()>
0x000000000040079b <+53>: mov [=10=]x600d00,%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/weakness。