NSPR 关于线程突然终止的立场¶
本备忘录描述了我对一项目前正在讨论是否纳入 NetScape 可移植运行时 (NSPR) 的功能的立场;线程突然退出的能力。我反对将此功能包含在 NSPR 中,因为它会导致不良的编程实践和无法维护的程序。
线程不是进程。
UNIX/C 环境中已经提供了一段时间的突然终止功能(exit()
),我假设那里定义的基本语义也适用于此。在该环境中,可以随时调用exit()
,并导致调用线程立即终止。在定义它的情况下(UNIX),它只有一个执行线程,这等同于终止进程。然后,进程抽象负责关闭所有打开的文件并回收在进程生命周期内可能分配的所有存储。
此实践不适用于线程。线程在进程的范围内运行(或其他环境中的类似抽象)。线程之所以轻量级,是因为它们不维护进程提供的完整保护域。因此,在多线程环境中,UNIX 的exit()
的对应项是什么?
NSPR 定义了一个可以在进程中任何时间由任何线程调用的函数,称为PR_ProcessExit()
。这与 UNIX 的exit()
相同,并且这样命名是为了使显而易见的事情更加明显。调用时,进程退出,关闭文件并回收进程的存储。
某些人对 NSPR 未提供仅退出特定线程的功能等效项感到失望。显然,他们没有考虑其后果。如果线程要突然终止,则没有记录它拥有哪些资源,因此应该回收这些资源。事实上,这些资源归进程所有,并由进程中的所有线程共享。
在使用线程进行编程的一般过程中,线程拥有它并且只有它知道的资源是非常有利的。在自然过程中,这些资源将由线程分配,使用一段时间,然后在栈展开时释放。在这些情况下,数据的出现仅记录在栈上,仅由单个线程知道(通常称为封装)。
突然终止的问题在于它可能在任何时候发生,发生在正确编码以处理正常和异常情况的线程上,但将无法做到这一点,因为它会被拒绝完成执行的机会。它可能发生是因为它从自己的范围调用到一些延迟实现的库中。
NSPR 对此的答案是没有突然的线程终止。所有线程都必须展开并从其根函数返回。如果它们由于某些状态损坏而无法做到这一点,那么它们必须假设损坏(如状态)是共享的,并且它们唯一的资源是终止进程。
要使此解决方案起作用,需要设计一个遇到错误的函数,使其首先修复其直接状态,然后将其错误报告给其调用方。如果调用方无法处理故障,则必须执行相同的操作。此过程将持续进行,直到线程从故障中恢复或从根函数返回。这并不困难(尽管我已经多次对现有代码执行过此操作,我承认这也不太有趣)。
在 NSPR 运行时中实现任何一种策略都不难。这不是本备忘录的重点。这是关于提供一个 API,该 API 尽可能多地引导人们做正确的事情。UNIX/C 环境中exit()
的存在是程序员将使用最便捷的可用解决方案的完美示例。C 语言的定义使得从main()
返回是一件非常好的事情。但是,有多少 C 程序真正费心去做呢?在 UNIX 中,凭借其对保护域的复杂定义,它碰巧可以工作(甚至可以说它更有效)从任何地方退出。但是线程不是进程。如果线程必须维护与进程相同类型的资源知识,它们就会失去所有好处。
线程是一种实现策略,用于提供在进程中并发执行的错觉。它们是具有大部分非阻塞库函数的大型状态机的替代方案。当后者用于提供并发时,调用exit()
将终止整个进程。为什么有人会期望线程的行为有所不同?线程不是进程。