c++ - 为什么我的线程在调用 join 后程序终止之前不能正确结束?
问题描述
我创建了一个包含单例类的共享库 (DLL)。单例类在构造时创建一个线程,并在析构函数中调用 join。请参阅下面的代码。
当我在另一个程序 (main.cpp) 中使用 DLL 并获取单例实例时,将创建线程并按预期运行。当程序终止时,调用单例析构函数,调用线程连接,但线程没有完成。
我从程序中得到的输出:
MySingleton::runner start
a
MySingleton::~MySingleton begin.
MySingleton::~MySingleton before calling join()
MySingleton::~MySingleton after calling join()
MySingleton::~MySingleton end.
预期输出:
MySingleton::runner start
a
MySingleton::~MySingleton begin.
MySingleton::~MySingleton before calling join()
MySingleton::runner end.
MySingleton::~MySingleton after calling join()
MySingleton::~MySingleton end.
在某些情况下,线程按我的预期结束(并且我得到了预期的输出):
- MySingleton::getInstance 在标头中定义
- 该库被编译为 .lib 而不是 .dll(静态库)
MySingleton singleton
主体中的定义(不是单例)
我无法弄清楚为什么线程仅在某些情况下没有按预期结束,而且我不明白这是否与 MSVC 有关,静态成员函数中的静态局部变量被破坏的方式,或者它是否是什么与我如何创建/加入线程或其他事情有关。
编辑
发生预期输出的更多情况:
定义volatile bool running_{false}
(可能不是正确的解决方案)定义std::atomic_bool running_{false}
似乎是正确的方法,或者使用互斥锁。
编辑 2
对变量使用 std::atomicrunning_
不起作用(尽管下面调整了代码以使用它,因为我们不想要 UB)。我在测试 std::atomic 和 volatile 时不小心将其构建为静态库,如前所述,静态库不会出现此问题。
我也尝试过running_
使用互斥锁进行保护,但仍然有奇怪的行为。while(true)
(我在一个循环中获得了一个锁,检查!running_
到break
。)
我还更新了下面的线程循环来增加一个计数器,析构函数将打印这个值(显示循环实际上正在执行)。
// Singleton.h
class MySingleton
{
private:
DllExport MySingleton();
DllExport ~MySingleton();
public:
DllExport static MySingleton& getInstance();
MySingleton(MySingleton const&) = delete;
void operator=(MySingleton const&) = delete;
private:
DllExport void runner();
std::thread th_;
std::atomic_bool running_{false};
std::atomic<size_t> counter_{0};
};
// Singleton.cpp
MySingleton::MySingleton() {
running_ = true;
th_ = std::thread(&MySingleton::runner, this);
}
MySingleton::~MySingleton()
{
std::cout << __FUNCTION__ << " begin." << std::endl;
running_ = false;
if (th_.joinable())
{
std::cout << __FUNCTION__ << " before calling join()" << std::endl;
th_.join();
std::cout << __FUNCTION__ << " after calling join()" << std::endl;
}
std::cout << "Count: " << counter_ << std::endl;
std::cout << __FUNCTION__ << " end." << std::endl;
}
MySingleton &MySingleton::getInstance()
{
static MySingleton single;
return single;
}
void MySingleton::runner()
{
std::cout << __FUNCTION__ << " start " << std::endl;
while (running_)
{
counter_++;
}
std::cout << __FUNCTION__ << " end " << std::endl;
}
// main.cpp
int main()
{
MySingleton::getInstance();
std::string s;
std::cin >> s;
return 0;
}
// DllExport.h
#ifdef DLL_EXPORT
#define DllExport __declspec(dllexport)
#else
#define DllExport __declspec(dllimport)
#endif
cmake_minimum_required(VERSION 3.13)
project("test")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTE NSIONS OFF)
add_library(singleton SHARED Singleton.cpp)
target_compile_definitions(singleton PUBLIC -DDLL_EXPORT)
target_include_directories(singleton PUBLIC ./)
install(TARGETS singleton
EXPORT singleton-config
CONFIGURATIONS ${CMAKE_BUILD_TYPE}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/
DESTINATION bin
FILES_MATCHING
PATTERN "*.dll"
PATTERN "*.pdb"
)
add_executable(main main.cpp )
target_link_libraries(main PUBLIC singleton)
install(TARGETS main RUNTIME DESTINATION bin)
解决方案
问题似乎是线程被ExitProcessmain
突然终止(返回 IIRC时隐式调用)。
退出进程会导致以下情况:
- 进程中的所有线程(调用线程除外)都会终止它们的执行,而不会收到 DLL_THREAD_DETACH 通知。
- 在步骤 1 中终止的所有线程的状态都变为信号状态。
- 所有加载的动态链接库 (DLL) 的入口点函数都使用 DLL_PROCESS_DETACH 调用。
- 在所有附加的 DLL 都执行了任何进程终止代码后,ExitProcess 函数会终止当前进程,包括调用线程。
- 调用线程的状态变为信号状态。
- 进程打开的所有对象句柄都已关闭。
- 进程的终止状态从 STILL_ACTIVE 变为进程的退出值。
- 进程对象的状态变为信号状态,满足任何一直在等待进程终止的线程。
运行器线程在 (1) 中终止,而析构函数仅在 (3) 中调用。
推荐阅读
- angular - 如何通过路由从有孩子的路径中获取 id?
- python - 在 Python 的 Matplotlib 中的线图和条形图之间共享 X 轴
- java - 使用 Lombok 库,但如何自定义/覆盖 setter 方法
- c# - HttpContext.GetOwinContext() 不包含 GetOwinContext() 的定义?
- mysql - 是否可以将此“join”和“as”查询转换为 Laravel PDO 函数?
- html - 使用边框折叠在网格中的行上添加边框:分离
- python - Sympy 中使用 MatrixSymbol 的虚矩阵
- sqlalchemy - 项目中的 Alembic、SQLAlchemy 和多个“应用程序”
- javascript - 如果没有输入值,如何检查并返回消息
- javascript - JS 智能舍入小数