比 main 长寿的单身人士
Singletons that outlive main
“魔术静态”单例方法通常非常有效:
T& instance() {
static T inst;
return inst;
}
以线程安全的方式,它在第一次调用时创建 T
,并且该对象一直存在到程序关闭。但是,如果这是一个日志记录对象,并且如果有比 main 更持久的后台线程,这可能会崩溃。我看到有人提到了这个问题,但没有提到解决方案:
- Portable C++ Singleton - When is the destructor called
这是可行的方法吗?:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
据我了解,只要每个线程在 main 退出之前调用 T::instance()
,该线程就会正确设置 threadInst
,只要调用线程是 运行.
问题:
- 对吗?只要
T::instance()
在 main()
开始之后和 main()
结束之前首先被每个线程调用,上面会避免 UB 吗?
- 有没有办法放松这个限制?我不知道有。特别是,我想不出一种方法来破坏
inst
与其他线程进行通信。
其他问题没有提供解决方案的原因是在大多数情况下不可能有用。允许线程超过 main 很难编写和维护。一些规则是:
[basic.start.term/4] If a function contains a block variable of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed block variable.
因此,如果不能保证在静态存储销毁之前执行,则不能使用函数局部静态。
[basic.start.term/6] If there is a use of a standard library object or function not permitted within signal handlers that does not happen before completion of destruction of objects with static storage duration and execution of std::atexit
registered functions, the program has undefined behavior.
所以,如果你不能保证在静态存储销毁之前执行,你也不能使用大多数标准库。
[support.signal/3.1] [Note 1: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note]
因此您不能直接或间接使用标准的新建或删除。
回到你的例子:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
除非你与线程协调,所以它被调用并且 thread_local
在 detach
之前构造,你只是将一个种族替换为另一个种族。要以这种方式稳健地执行某些操作,您必须等待条件变量,以便线程在分离之前通知您 thread_local 已初始化。但是,如果这样做,直接将指针传递给线程通常会更简单。此外,信号处理程序中不允许 shared_ptr
,因此它不是可用于此目的的类型。
所以,这是我的指导方针:
- T 或它使用的任何东西或在静态存储破坏之前不能保证发生的调用不能使用任何非信号安全的东西,包括大多数标准库。
- 将指向 T 的指针或引用传递给线程。它不能通过函数局部静态或全局静态变量访问。它必须在相关线程中保持为 thread_local 或自动。
- 泄漏它。您不能销毁它,因为在此上下文中不允许删除。
“魔术静态”单例方法通常非常有效:
T& instance() {
static T inst;
return inst;
}
以线程安全的方式,它在第一次调用时创建 T
,并且该对象一直存在到程序关闭。但是,如果这是一个日志记录对象,并且如果有比 main 更持久的后台线程,这可能会崩溃。我看到有人提到了这个问题,但没有提到解决方案:
- Portable C++ Singleton - When is the destructor called
这是可行的方法吗?:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
据我了解,只要每个线程在 main 退出之前调用 T::instance()
,该线程就会正确设置 threadInst
,只要调用线程是 运行.
问题:
- 对吗?只要
T::instance()
在main()
开始之后和main()
结束之前首先被每个线程调用,上面会避免 UB 吗? - 有没有办法放松这个限制?我不知道有。特别是,我想不出一种方法来破坏
inst
与其他线程进行通信。
其他问题没有提供解决方案的原因是在大多数情况下不可能有用。允许线程超过 main 很难编写和维护。一些规则是:
[basic.start.term/4] If a function contains a block variable of static or thread storage duration that has been destroyed and the function is called during the destruction of an object with static or thread storage duration, the program has undefined behavior if the flow of control passes through the definition of the previously destroyed block variable.
因此,如果不能保证在静态存储销毁之前执行,则不能使用函数局部静态。
[basic.start.term/6] If there is a use of a standard library object or function not permitted within signal handlers that does not happen before completion of destruction of objects with static storage duration and execution of
std::atexit
registered functions, the program has undefined behavior.
所以,如果你不能保证在静态存储销毁之前执行,你也不能使用大多数标准库。
[support.signal/3.1] [Note 1: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. — end note]
因此您不能直接或间接使用标准的新建或删除。
回到你的例子:
/*static*/ T& T::instance() {
static auto inst = std::make_shared<T>();
static thread_local auto threadInst = inst; // Hope this call gets hit before `inst` goes out of scope.
return *threadInst;
}
除非你与线程协调,所以它被调用并且 thread_local
在 detach
之前构造,你只是将一个种族替换为另一个种族。要以这种方式稳健地执行某些操作,您必须等待条件变量,以便线程在分离之前通知您 thread_local 已初始化。但是,如果这样做,直接将指针传递给线程通常会更简单。此外,信号处理程序中不允许 shared_ptr
,因此它不是可用于此目的的类型。
所以,这是我的指导方针:
- T 或它使用的任何东西或在静态存储破坏之前不能保证发生的调用不能使用任何非信号安全的东西,包括大多数标准库。
- 将指向 T 的指针或引用传递给线程。它不能通过函数局部静态或全局静态变量访问。它必须在相关线程中保持为 thread_local 或自动。
- 泄漏它。您不能销毁它,因为在此上下文中不允许删除。