内存泄漏更喜欢为了不减慢关机

Memory leaks preferring in order to not to slow down shutdown

Chromium's documentation says:

NOTE: Both Singleton and base::LazyInstance provide "leaky" traits to leak the global on shutdown. This is often advisable (except potentially in library code where the code may be dynamically loaded into another process's address space or when data needs to be flushed on process shutdown) in order to not to slow down shutdown.

在什么情况下这是可以接受的?

无论如何,当所有内存(和其他资源)都被回收并释放回 OS 时。例如,这就是 linux 系统上发生的情况。

"Acceptable" 有点难以定义,因为我个人更喜欢避免这种做法。例如,我们使用泄漏检测工具,除非以其他方式抑制,否则这种做法最终会显示误报(尽管我在 Chromium 的文档中注意到它们的泄漏特征抑制了它:我不这样做并将误报警告视为需要修复的东西而不是压制)。

当然,只有当目标操作系统在关闭进程时回收单例获得的资源时,这才是可接受的。大多数现代操作系统都会为最常见的资源类型执行此操作。当然,如果您的单例创建了某种类型的大量临时文件,明智的做法是通过显式关闭进程销毁它。

关机速度

简单地避免手动回收资源以快速关闭的引用理由是合理的,但它通常伴随着一种心态,即在最小的块中分配大量内存。

当使用链接结构或大量针对通用分配器单独分配的极小抽象对象时,通常会出现这种情况。如果有数百万个极小的内存块需要单独释放,这可能会转化为痛苦的几秒钟的关机时间。

然而,启动速度、加载输入数据和关闭速度往往相互关联。如果我们想加快所有这些速度,批量预分配和池化内存会很有帮助。

通过这种批量资源分配和池策略,应用程序可以启动和关闭,并且总体上可以快速运行。这可能很难应用,但如果你有它,即使加载了千兆字节的数据,应用程序通常也可以 insta-shutdown,并且可能只是泄漏资源开始看起来比优化技术更懒惰并可能将其推向 "unacceptable" 的审美。 YMMV.

然而,在确实需要性能的情况下,您会做您需要做的事情。我想 Chromium 开发人员在为泄漏检测器设计泄漏特性和抑制器之前,将他们的代码库结构化为一种有益的性质。每个人都有不同的场景。

关机正确性

在一些非常复杂的代码库中,有很多插件在关闭时被加载和卸载,例如,让该死的应用程序正确关闭实际上真的很难。

我曾经低头看过一个代码库,我们以这种方式将自己逼入绝境,实际上我们的大部分错误都与应用程序在关闭时崩溃有关(崩溃的用户关键时间最少,但仍然非常烦人) .

我们的问题是加载插件时创建的单例和其他类型的全局变量,在创建时会触发对外部状态的副作用(其他全局变量或生活在不同模块中的其他单例)。在那些情况下,有一种对称的愿望,即在破坏这样一个全局时扭转这些副作用(例如:如果将全局添加到一个中,则从全局集合中删除)。这引入了销毁顺序的问题(由于众多作者编写的分散代码和分散在众多插件中的全球状态,这在这里非常困难)。

当全局变量在销毁过程中开始依赖于其他全局变量(惰性构造或非惰性构造)时,只要确保事物以正确的顺序被销毁,这样一个全局变量就不会最终依赖另一个全局变量,事情就会变得非常尴尬在关闭期间已经失效(可能甚至卸载了 dylib)。

我们几乎肯定在那里做错了各种各样的事情(从使用太多的单例和全局变量开始,然后以没有统一设计思维的临时方式将它们分散在插件中),这种经历让我不喜欢单例和这些复杂场景中的各种全局变量(不是因为灵活性降低,而是编写正确的关闭代码的困难),但如果我们不费心尝试使这些全局变量反转,它可能会容易得多对其他全局变量的破坏副作用,只需将它们留在那里并让操作系统清理资源。在那里,我可以理解只是泄漏而不去破坏东西的诱惑,或者即使我们确实破坏了东西,也不会逆转对其他全局变量的副作用。这是一个非常错误的设计的丑陋解决方法,但我可以理解这种问题比通过泄漏解决性能问题要容易一些。

无论如何,"acceptable" 的变化会很大。它的范围从美学一直到便携性问题。这是我第一次遇到关于简单泄漏单例的效率论证,但我能理解什么样的情况可能会导致这种情况。