我在一个通常构build为共享库的产品上工作。
使用的应用程序将加载它,创build一些句柄,使用它们,并最终释放所有句柄并卸载库。
该库创build一些后台线程,通常在释放句柄的地方停止。
现在,问题是一些消费应用程序不是超级行为,并且在某些情况下(取消,错误等)将无法释放句柄。 最终,我们的库中的静态析构函数会运行,并在尝试与(现在已死的)后台线程进行交互时发生崩溃。
一种可能是没有析构函数的全局对象,所以避免在静态破坏期间运行库中的任何代码。 这可能会解决进程退出的崩溃,但是它会在应用程序简单地卸载库而不释放句柄(而不是退出)的情况下引入泄漏和崩溃,因为我们不能确保后台线程实际上是在他们正在运行的代码被卸载之前停止。
更重要的是,就我所知,当main()退出时,所有其他的线程都会被杀死,无论这些线程在哪里碰巧都会被locking,并且不variables被破坏(例如在堆pipe理器中)。
鉴于此,试图支持这些错误的应用程序是否有意义?
是的,你的图书馆应该允许这个过程没有警告地退出。 也许在一个理想的世界里,每一个使用你的图书馆的程序都会仔细地跟踪句柄,并在任何理由退出时释放它们,但实际上这不是一个现实的要求。 触发程序退出的代码路径可能是一个共享组件,甚至不知道您的库正在使用中!
在任何情况下,您当前的体系结构可能存在一个更为普遍的问题,因为静态析构函数与其他线程交互本质上是不安全的。
从MSDN的DllMain入口点 :
因为DLL通知是序列化的,所以入口函数不应该尝试与其他线程或进程通信。 结果可能会发生死锁。
和
如果您的DLL与C运行时库(CRT)链接,则CRT提供的入口点将调用全局和静态C ++对象的构造函数和析构函数。 因此,对DllMain的这些限制也适用于构造函数和析构函数以及从它们调用的任何代码。
特别是,如果析构函数试图等待线程退出,那么在线程仍在运行的情况下显式卸载了库的情况下,几乎肯定会发生死锁。 如果析构函数不等待,线程正在运行的代码消失,进程将崩溃。 我不知道为什么你没有看到这个问题, 也许你正在终止线程? (虽然原因不同,但这也不安全。)
有很多方法可以解决这个问题。 也许最简单的是你已经提到的那个:
一种可能是没有析构函数的全局对象,所以避免在静态破坏期间运行库中的任何代码。
你继续说:
但它会引入泄漏和崩溃的情况下,应用程序简单地卸载图书馆,而不释放手柄[…]
那不是你的问题! 只有应用程序明确选择才能卸载该库; 显然,与前面的场景不同的是,有问题的代码知道你的库存在,因此在你这样做之前要求它关闭所有的句柄是完全合理的。
但是,理想情况下,您将提供一个自动关闭所有句柄的非初始化函数,而不是要求应用程序分别关闭每个句柄。 显式初始化和非初始化函数还允许您安全地设置和释放全局资源,这通常比以每个句柄为基础执行所有设置和拆卸更为有效,并且比使用全局对象更安全。
(请参阅上面的链接,了解适用于静态构造函数和析构函数的所有限制的全部描述;它们非常广泛,在显式的初始化例程中构建所有的全局变量,并在显式的初始化例程中销毁它们,避免了整个混乱的业务。 )