You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用join后DLL单例的线程未在程序终止前正常结束是什么原因?

问题现象

开发中编写了包含单例类的动态链接库(DLL),单例类构造函数创建工作线程,析构函数调用join等待线程结束。主程序调用getInstance()获取实例时线程可正常创建运行,但程序终止触发单例析构,执行join后线程未完成执行,缺少MySingleton::runner end日志,与预期不符。

实际输出

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实例,不使用单例模式

补充说明:最初测试误以为用volatile bool running_{false}或std::atomic_bool running_{false}定义running_可解决问题,后续发现测试时误将库编译为静态库,实际在动态库场景下,使用std::atomic修饰running_、用mutex保护running_访问都无法解决该问题,析构函数打印的counter值可证明线程循环此前一直在正常执行。

相关代码

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

CMakeLists.txt

cmake_minimum_required(VERSION 3.13)
project("test")

set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS 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)
原因解答

该问题是Windows平台MSVC环境下的DLL卸载逻辑与静态对象析构顺序冲突导致的:

  1. Windows程序退出时,先执行主程序所有静态/全局对象的析构,之后调用ExitProcess,系统会按加载顺序的逆序卸载所有已加载的DLL。
  2. 你的单例是定义在DLL的getInstance函数内的静态局部变量,属于DLL范围内的静态对象,它的析构是在DLL被卸载时才触发的。
  3. 当DLL进入卸载流程时,MSVC的C++运行时会先执行当前DLL内所有静态对象的析构,但此时DLL的代码段、数据段已经进入待回收状态,DLL中创建的非主线程会被系统强制终止,不会再继续执行后续代码。
  4. 你在单例析构里设置running_ = false之后调用join,但是此时工作线程已经被系统提前杀掉,自然不会执行到runner里退出循环后的MySingleton::runner end打印,join也会直接返回。

其他场景正常的原因

  • getInstance定义在头文件:此时静态局部单例的实例会被创建在主程序的运行时上下文里,析构时机是主程序退出时、DLL卸载之前,此时DLL的代码段还处于正常可用状态,线程可以正常执行完退出逻辑。
  • 编译为静态库:静态库的代码会直接链接到主程序镜像中,单例属于主程序的静态对象,析构在主程序退出流程中完成,不会触发DLL卸载的提前杀线程逻辑。
  • 主函数直接定义实例:实例属于主程序的栈对象,main函数返回前就会析构,远早于DLL卸载流程,线程自然可以正常执行完成。
解决方案

可任选一种方案规避问题:

  • 给单例加一个主动销毁的静态方法,在main函数返回前显式调用,提前触发单例析构和线程join,不要等DLL卸载时自动析构。
  • 将getInstance的实现移到头文件中,让单例实例创建在主程序的上下文里。
  • 改用静态库编译,避免DLL卸载流程的影响。

内容的提问来源于stack exchange,提问作者Braiden Cutforth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 10:39:03