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

2024-01-10 17:52

本文主要是介绍与AI合作 -- 单例工厂2遗留的问题:bard的错误,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问题

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


class Singleton {
public:static Singleton& getInstance() {std::lock_guard<std::mutex> lock(mutex); // Acquire lock for thread safetystatic Singleton instance; // Local static variable for thread safetyreturn 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   %rbp0x0000000000400e27 <+1>:     mov    %rsp,%rbp0x0000000000400e2a <+4>:     push   %r120x0000000000400e2c <+6>:     push   %rbx0x0000000000400e2d <+7>:     mov    $0x401808,%esi0x0000000000400e32 <+12>:    mov    $0x6030c0,%edi0x0000000000400e37 <+17>:    callq  0x400ca0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>0x0000000000400e3c <+22>:    mov    $0x400c40,%esi0x0000000000400e41 <+27>:    mov    %rax,%rdi0x0000000000400e44 <+30>:    callq  0x400cd0 <_ZNSolsEPFRSoS_E@plt>0x0000000000400e49 <+35>:    movzbl 0x202390(%rip),%eax        # 0x6031e0 <_ZGVZ4testvE3pro>0x0000000000400e50 <+42>:    test   %al,%al0x0000000000400e52 <+44>:    sete   %al0x0000000000400e55 <+47>:    test   %al,%al0x0000000000400e57 <+49>:    je     0x400e86 <test()+96>0x0000000000400e59 <+51>:    mov    $0x6031e0,%edi0x0000000000400e5e <+56>:    callq  0x400d20 <__cxa_guard_acquire@plt>0x0000000000400e63 <+61>:    test   %eax,%eax0x0000000000400e65 <+63>:    setne  %al0x0000000000400e68 <+66>:    test   %al,%al0x0000000000400e6a <+68>:    je     0x400e86 <test()+96>0x0000000000400e6c <+70>:    mov    $0x0,%r12d0x0000000000400e72 <+76>:    mov    $0x6031d9,%edi0x0000000000400e77 <+81>:    callq  0x40103a <Product::Product()>0x0000000000400e7c <+86>:    mov    $0x6031e0,%edi0x0000000000400e81 <+91>:    callq  0x400c70 <__cxa_guard_release@plt>0x0000000000400e86 <+96>:    mov    $0x401817,%esi0x0000000000400e8b <+101>:   mov    $0x6030c0,%edi0x0000000000400e90 <+106>:   callq  0x400ca0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>0x0000000000400e95 <+111>:   mov    $0x400c40,%esi0x0000000000400e9a <+116>:   mov    %rax,%rdi0x0000000000400e9d <+119>:   callq  0x400cd0 <_ZNSolsEPFRSoS_E@plt>0x0000000000400ea2 <+124>:   jmp    0x400ec1 <test()+155>0x0000000000400ea4 <+126>:   mov    %rax,%rbx0x0000000000400ea7 <+129>:   test   %r12b,%r12b0x0000000000400eaa <+132>:   jne    0x400eb6 <test()+144>0x0000000000400eac <+134>:   mov    $0x6031e0,%edi0x0000000000400eb1 <+139>:   callq  0x400c60 <__cxa_guard_abort@plt>0x0000000000400eb6 <+144>:   mov    %rbx,%rax0x0000000000400eb9 <+147>:   mov    %rax,%rdi0x0000000000400ebc <+150>:   callq  0x400d10 <_Unwind_Resume@plt>0x0000000000400ec1 <+155>:   pop    %rbx0x0000000000400ec2 <+156>:   pop    %r120x0000000000400ec4 <+158>:   pop    %rbp0x0000000000400ec5 <+159>:   retq

简单提一下_ZGVZ4testvE3pro demangle一下(第一次值是0,初始化后是1):

[mzhai@singletonFactoryAi]$ c++filt _ZGVZ4testvE3pro
guard variable for test()::pro

 着重看下面的__cxa_guard_acquire

看到__cxa_guard_acquire、__cxa_guard_release没?中间夹着Product::Product()

即使没见过这两个函数,也能猜到和static变量初始化有关,防止多个线程同时初始化一个静态变量. 这是我把__cxa_guard_acquire输入搜索引擎搜到的:

C++ constructor guards for static instances

Introduction

The libstd++ ABI requires implementations of the following functions:

int __cxa_guard_acquire(__guard *g);

void __cxa_guard_release(__guard *g);

void __cxa_guard_abort(__guard *g);

These functions are not intended to be ever used by the programmer, instead the C++ compiler will emit code calling them if statically allocated class instances are used. In a multi-threaded environment special care needs to be taken to prevent race conditions while initializing and using such instances. This modules provides them.

Usage

This module is intended to be used by platforms that want to provide C++ support, but the used standard C++ library does not provide these guards. In this case, adding this module will do the trick. The programmer / user should never interact with any of the functions.

Note that on some platforms the type __guard is defined differently from the "generic" definition, most notably ARM. For those platforms a header named cxx_ctor_guards_arch.h needs to be created containing the correct typedef and the preprocessor macro CXX_CTOR_GUARDS_CUSTOM_TYPE needs to be defined.

https://opensource.apple.com/source/libcppabi/libcppabi-14/src/cxa_guard.cxx 

int __cxxabiv1::__cxa_guard_acquire(uint64_t* guard_object)
{// Double check that the initializer has not already been runif ( initializerHasRun(guard_object) )return 0;// We now need to acquire a lock that allows only one thread// to run the initializer.  If a different thread calls// __cxa_guard_acquire() with the same guard object, we want // that thread to block until this thread is done running the // initializer and calls __cxa_guard_release().  But if the same// thread calls __cxa_guard_acquire() with the same guard object,// we want to abort.  // To implement this we have one global pthread recursive mutex // shared by all guard objects, but only one at a time.  

用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.6Thread 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_acquireId   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.63    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的回答

mzhai: Assume there is a static variable A which is an object of a class, this static variable is defined in a function named funA. When is A initialized? and is there a race condition if two threads call funA concurrently?

ChatGPT: In C++, static variables inside functions (local static variables) are initialized the first time the program execution encounters their declaration within that function. They're initialized in a thread-safe manner in compliance with the C++11 standard.

For instance, if you have a function funA with a static variable A inside it, like this:

void funA() { static MyClass A; // ... } 

When funA is called for the first time, the static MyClass A; line will be executed. This initialization occurs once, and subsequent calls to funA won't reinitialize A. This initialization is thread-safe in C++11 and later, meaning the standard guarantees that it will be properly handled in a multi-threaded context. There won't be a race condition during this initialization.

Bard

Here's a breakdown of the initialization and potential race condition:

1. Initialization of static variable A:

  • When: The static variable A will be initialized the first time control flow reaches its declaration within funA, even if it's not explicitly used in that particular call.
  • How: The constructor of the class will be invoked to initialize A's members.
  • Subsequent calls: In subsequent calls to funA, the already initialized instance of A will be used, and the constructor won't be called again.

2. Race condition in multi-threaded environment:

  • Yes, a race condition can occur if two threads call funA concurrently before A has been initialized.
  • Scenario:
    • Both threads enter funA for the first time.
    • Both threads see that A hasn't been initialized yet.
    • Both threads attempt to create an instance of A, leading to multiple initializations and potential data corruption or crashes.

 可见在这方面Bard不如Chat GPT正确。

他山之石

后来搜到一篇外文博客,读者可以作为参考。Adventures in Systems Programming: C++ Local Statics - In Pursuit of Laziness

这篇关于与AI合作 -- 单例工厂2遗留的问题:bard的错误的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/591602

相关文章

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

MySQL 设置AUTO_INCREMENT 无效的问题解决

《MySQL设置AUTO_INCREMENT无效的问题解决》本文主要介绍了MySQL设置AUTO_INCREMENT无效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录快速设置mysql的auto_increment参数一、修改 AUTO_INCREMENT 的值。

关于跨域无效的问题及解决(java后端方案)

《关于跨域无效的问题及解决(java后端方案)》:本文主要介绍关于跨域无效的问题及解决(java后端方案),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录通用后端跨域方法1、@CrossOrigin 注解2、springboot2.0 实现WebMvcConfig

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Java死锁问题解决方案及示例详解

《Java死锁问题解决方案及示例详解》死锁是指两个或多个线程因争夺资源而相互等待,导致所有线程都无法继续执行的一种状态,本文给大家详细介绍了Java死锁问题解决方案详解及实践样例,需要的朋友可以参考下... 目录1、简述死锁的四个必要条件:2、死锁示例代码3、如何检测死锁?3.1 使用 jstack3.2

解决JSONField、JsonProperty不生效的问题

《解决JSONField、JsonProperty不生效的问题》:本文主要介绍解决JSONField、JsonProperty不生效的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录jsONField、JsonProperty不生效javascript问题排查总结JSONField

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三

MySQL版本问题导致项目无法启动问题的解决方案

《MySQL版本问题导致项目无法启动问题的解决方案》本文记录了一次因MySQL版本不一致导致项目启动失败的经历,详细解析了连接错误的原因,并提供了两种解决方案:调整连接字符串禁用SSL或统一MySQL... 目录本地项目启动报错报错原因:解决方案第一个:第二种:容器启动mysql的坑两种修改时区的方法:本地

springboot加载不到nacos配置中心的配置问题处理

《springboot加载不到nacos配置中心的配置问题处理》:本文主要介绍springboot加载不到nacos配置中心的配置问题处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录springboot加载不到nacos配置中心的配置两种可能Spring Boot 版本Nacos

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co