0
点赞
收藏
分享

微信扫一扫

与AI合作 -- 单例工厂2遗留的问题:bard的错误

问题

上一节我们针对函数内静态变量初始化在多线程环境中要不要用锁保护提出了疑问,代码如下:


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

举报

相关推荐

0 条评论