首页 > 解决方案 > 为什么我的线程在调用 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.

在某些情况下,线程按我的预期结束(并且我得到了预期的输出):

  1. MySingleton::getInstance 在标头中定义
  2. 该库被编译为 .lib 而不是 .dll(静态库)
  3. MySingleton singleton主体中的定义(不是单例)

我无法弄清楚为什么线程仅在某些情况下没有按预期结束,而且我不明白这是否与 MSVC 有关,静态成员函数中的静态局部变量被破坏的方式,或者它是否是什么与我如何创建/加入线程或其他事情有关。

编辑

发生预期输出的更多情况:

  1. 定义volatile bool running_{false}(可能不是正确的解决方案)
  2. 定义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)

标签: c++visual-c++dll

解决方案


问题似乎是线程被ExitProcessmain突然终止(返回 IIRC时隐式调用)。

退出进程会导致以下情况:

  1. 进程中的所有线程(调用线程除外)都会终止它们的执行,而不会收到 DLL_THREAD_DETACH 通知。
  2. 在步骤 1 中终止的所有线程的状态都变为信号状态。
  3. 所有加载的动态链接库 (DLL) 的入口点函数都使用 DLL_PROCESS_DETACH 调用。
  4. 在所有附加的 DLL 都执行了任何进程终止代码后,ExitProcess 函数会终止当前进程,包括调用线程。
  5. 调用线程的状态变为信号状态。
  6. 进程打开的所有对象句柄都已关闭。
  7. 进程的终止状态从 STILL_ACTIVE 变为进程的退出值。
  8. 进程对象的状态变为信号状态,满足任何一直在等待进程终止的线程。

运行器线程在 (1) 中终止,而析构函数仅在 (3) 中调用。


推荐阅读