问题
上一节我们针对函数内静态变量初始化在多线程环境中要不要用锁保护提出了疑问,代码如下:
class Singleton {
public:
static Singleton& getInstance() {
std::lock_guard<std::mutex> lock(mutex); // Acquire lock for thread safety
static Singleton instance; // Local static variable for thread safety
return instance;
结论
不必。C++11已经自己加锁了。
__cxa_guard_acquire
为了简单,我们先简单写个试验程序:
class Product {
public:
Product(){
std::cout<<"Product constructor"<<std::endl;
}
};
void test(){
std::cout<<"I am in test()"<<std::endl;
static Product pro;
std::cout<<"leaving test()"<<std::endl;
}
int main() {
std::thread first(test);
std::thread second(test);
first.join();
second.join();
test();
return 0;
}
直接用GDB或者objdump看下test函数对应的汇编:
(gdb) disass test
Dump of assembler code for function test():
0x0000000000400e26 <+0>: push %rbp
0x0000000000400e27 <+1>: mov %rsp,%rbp
0x0000000000400e2a <+4>: push %r12
0x0000000000400e2c <+6>: push %rbx
0x0000000000400e2d <+7>: mov $0x401808,%esi
0x0000000000400e32 <+12>: mov $0x6030c0,%edi
0x0000000000400e37 <+17>: callq 0x400ca0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400e3c <+22>: mov $0x400c40,%esi
0x0000000000400e41 <+27>: mov %rax,%rdi
0x0000000000400e44 <+30>: callq 0x400cd0 <_ZNSolsEPFRSoS_E@plt>
0x0000000000400e49 <+35>: movzbl 0x202390(%rip),%eax # 0x6031e0 <_ZGVZ4testvE3pro>
0x0000000000400e50 <+42>: test %al,%al
0x0000000000400e52 <+44>: sete %al
0x0000000000400e55 <+47>: test %al,%al
0x0000000000400e57 <+49>: je 0x400e86 <test()+96>
0x0000000000400e59 <+51>: mov $0x6031e0,%edi
0x0000000000400e5e <+56>: callq 0x400d20 <__cxa_guard_acquire@plt>
0x0000000000400e63 <+61>: test %eax,%eax
0x0000000000400e65 <+63>: setne %al
0x0000000000400e68 <+66>: test %al,%al
0x0000000000400e6a <+68>: je 0x400e86 <test()+96>
0x0000000000400e6c <+70>: mov $0x0,%r12d
0x0000000000400e72 <+76>: mov $0x6031d9,%edi
0x0000000000400e77 <+81>: callq 0x40103a <Product::Product()>
0x0000000000400e7c <+86>: mov $0x6031e0,%edi
0x0000000000400e81 <+91>: callq 0x400c70 <__cxa_guard_release@plt>
0x0000000000400e86 <+96>: mov $0x401817,%esi
0x0000000000400e8b <+101>: mov $0x6030c0,%edi
0x0000000000400e90 <+106>: callq 0x400ca0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400e95 <+111>: mov $0x400c40,%esi
0x0000000000400e9a <+116>: mov %rax,%rdi
0x0000000000400e9d <+119>: callq 0x400cd0 <_ZNSolsEPFRSoS_E@plt>
0x0000000000400ea2 <+124>: jmp 0x400ec1 <test()+155>
0x0000000000400ea4 <+126>: mov %rax,%rbx
0x0000000000400ea7 <+129>: test %r12b,%r12b
0x0000000000400eaa <+132>: jne 0x400eb6 <test()+144>
0x0000000000400eac <+134>: mov $0x6031e0,%edi
0x0000000000400eb1 <+139>: callq 0x400c60 <__cxa_guard_abort@plt>
0x0000000000400eb6 <+144>: mov %rbx,%rax
0x0000000000400eb9 <+147>: mov %rax,%rdi
0x0000000000400ebc <+150>: callq 0x400d10 <_Unwind_Resume@plt>
0x0000000000400ec1 <+155>: pop %rbx
0x0000000000400ec2 <+156>: pop %r12
0x0000000000400ec4 <+158>: pop %rbp
0x0000000000400ec5 <+159>: retq
简单提一下_ZGVZ4testvE3pro demangle一下(第一次值是0,初始化后是1):
[mzhai@singletonFactoryAi]$ c++filt _ZGVZ4testvE3pro
guard variable for test()::pro
着重看下面的__cxa_guard_acquire
即使没见过这两个函数,也能猜到和static变量初始化有关,防止多个线程同时初始化一个静态变量. 这是我把__cxa_guard_acquire输入搜索引擎搜到的:
C++ constructor guards for static instances
https://opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx
用GDB 模拟race condition
如果读者感兴趣,可以调试一下我们给出的程序:
(gdb) b __cxa_guard_acquire 使得两个线程都停在__cxa_guard_acquire
Breakpoint 2 at 0x7ffff7acf980
(gdb) r
Continuing.
Thread 2 "a.out" hit Breakpoint 2, 0x00007ffff7acf980 in __cxa_guard_acquire () from /lib64/libstdc++.so.6
Thread 3 "a.out" hit Breakpoint 2, 0x00007ffff7acf980 in __cxa_guard_acquire () from /lib64/libstdc++.so.6
Quit
(gdb) where
Selected thread is running.
(gdb) info thread 2、3线程都停在了__cxa_guard_acquire
Id Target Id Frame
* 1 Thread 0x7ffff7fe1740 (LWP 2468153) "a.out" (running)
2 Thread 0x7ffff6eb9700 (LWP 2468165) "a.out" 0x00007ffff7acf980 in __cxa_guard_acquire () from /lib64/libstdc++.so.6
3 Thread 0x7ffff66b8700 (LWP 2468166) "a.out" 0x00007ffff7acf980 in __cxa_guard_acquire () from /lib64/libstdc++.so.6
(gdb) b Product::Product thread 2
Breakpoint 3 at 0x401046: file static_var_in_func.cpp, line 36.
(gdb) thread apply 2 c 让第二个线程获得锁,先停在静态变量初始化中
Thread 2 (Thread 0x7ffff6eb9700 (LWP 2468165)):
Continuing.
Thread 2 "a.out" hit Breakpoint 3, Product::Product (this=0x6031d9 <test()::pro>) at static_var_in_func.cpp:36
36 std::cout<<"Product constructor"<<std::endl;
(gdb) thread apply 3 c 让第三个线程(还没获得锁)继续运行,应该卡在__cxa_guard_acquire
Thread 3 (Thread 0x7ffff66b8700 (LWP 2468166)):
Continuing.
^C
Thread 1 "a.out" received signal SIGINT, Interrupt.
0x00007ffff78226cd in __pthread_timedjoin_ex () from /lib64/libpthread.so.0
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff66b8700 (LWP 2468166))](running)
(gdb) where
Selected thread is running.
(gdb) interrupt
(gdb)
Thread 3 "a.out" stopped.
0x00007ffff72759bd in syscall () from /lib64/libc.so.6
Quit
(gdb) where 正如预期,第三个线程卡在获得锁上
#0 0x00007ffff72759bd in syscall () from /lib64/libc.so.6
#1 0x00007ffff7acfa3f in __cxa_guard_acquire () from /lib64/libstdc++.so.6
#2 0x0000000000400e63 in test () at static_var_in_func.cpp:42
#3 0x0000000000401205 in std::__invoke_impl<void, void (*)()> (__f=@0x616008: 0x400e26 <test()>) at /usr/include/c++/8/bits/invoke.h:60
#4 0x0000000000401085 in std::__invoke<void (*)()> (__fn=@0x616008: 0x400e26 <test()>) at /usr/include/c++/8/bits/invoke.h:95
#5 0x00000000004016d4 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x616008) at /usr/include/c++/8/thread:244
#6 0x00000000004016aa in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x616008) at /usr/include/c++/8/thread:253
#7 0x000000000040168e in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x616000) at /usr/include/c++/8/thread:196
#8 0x00007ffff7afbb23 in execute_native_thread_routine () from /lib64/libstdc++.so.6
#9 0x00007ffff78211ca in start_thread () from /lib64/libpthread.so.0
#10 0x00007ffff7275e73 in clone () from /lib64/libc.so.6
AI的回答
可见在这方面Bard不如Chat GPT正确。
他山之石
后来搜到一篇外文博客,读者可以作为参考。Adventures in Systems Programming: C++ Local Statics - In Pursuit of Laziness