何时 POSIX 线程取消不是立即的?

3
POSIX规定了两种线程取消类型:PTHREAD_CANCEL_ASYNCHRONOUSPTHREAD_CANCEL_DEFERRED(由pthread_setcanceltype(3)设置),确定pthread_cancel(3)何时生效。据我所知,POSIX手册并没有对此多加说明,但Linux手册对PTHREAD_CANCEL_ASYNCHRONOUS的说明如下:
引用:

线程可以在任何时候被取消。(通常情况下,在接收到取消请求后立即被取消,但系统不能保证这一点。)

我很好奇“系统不能保证这一点”的意思。我可以很容易地想象这种情况发生在多核/多CPU系统中(在上下文切换之前)。但是单核系统呢?
1. 当启用取消(pthread_setcancelstate(3))并将取消类型设置为PTHREAD_CANCEL_ASYNCHRONOUS时,我们是否可能有一个线程在请求取消时不会立即被取消? 2. 如果是,什么条件下会发生这种情况?
我主要关心Linux(LinuxThreads / NPTL),但也更普遍地关注符合POSIX标准的方式来看待这个取消业务。
更新/澄清: 实际上,这里真正的实际关注点是在调用pthread_cancel()后立即销毁的资源的使用,其中目标线程启用了取消并将其设置为类型PTHREAD_CANCEL_ASYNCHRONOUS!!!因此,关键问题是:在上下文切换后,即使只有很短的时间,被取消的线程在这种情况下是否仍可能继续正常运行?
感谢Damon的答案,问题被减少到与下一个上下文切换相关的信号传递和处理。
更新-2: 我回答了自己的问题,指出这是错误的问题,并且应该从根本上不同的概念层面解决底层程序设计。我希望这个“错误”的问题对于其他想知道异步取消奥秘的人有所帮助。
2个回答

8
意思就是它所说的那样:不能保证立即发生。原因是标准需要考虑实现细节上的某种“自由”。
例如,在Linux/NPTL下,取消操作是通过发送信号32来实现的。当接收到信号时,线程被取消,这通常会在下一个内核到用户的切换、下一个中断或时间片结束时(可能意外地立即结束,但通常不会)。然而,只有当线程正在运行时才会接收到信号。因此,真正的问题在于信号不一定会立即接收到。
如果仔细想想,也没有太多不同的方法。由于您可以使用phtread_cleanup_push一些处理程序,操作系统必须执行它们(它不能只是将线程从存在中爆炸!),因此线程必须运行才能被取消。没有任何保证,包括您要取消的线程在内的任何特定线程都在您取消线程的确切时间运行,因此不能保证立即取消。
除非,当然,假设操作系统以阻止调用线程并安排要取消的线程以便执行其处理程序的方式实现,并且仅在之后取消pthread_cancel。但由于pthread_cancel未指定为阻塞,因此这将是一个非常令人讨厌的惊喜。这也会干扰执行时间限制和调度程序公平性,因此这种方法是不可接受的。
因此,如果取消类型为“禁用”,则不会发生任何事情。或者,它是“启用”的,取消类型为“延迟”,则线程在调用被列为cancellation point的函数时取消。或者,它是“异步”的,那么如上所述,操作系统将在它认为适当的时间做“某些”来取消线程,而不是在精确定义的时间点,而是“很快”。在Linux中,通过发送信号来实现。

1
现在我也检查了uClibc内部的LinuxThreads,这是我使用交叉编译的目标,它的实现方法是相同的。感谢你精心思考的出色答案。线程取消是一件棘手的事情。 - FooF
很明显,在上下文切换之前线程是无法被销毁的,而且在 pthread_cancel() 调用之后上下文切换也不会立即发生。更加具体和实际的答案是:当信号被接收并且信号处理程序相对于下一个上下文切换运行时(在无法实现真正并发的单核架构中)? - FooF
1
这也在答案中(第二段)。信号(除非它们是硬件生成的,例如`SIGSEGV´,否则它们会在内核用户模式切换时接收到,或者当任务被调度时,或者当中断发生时接收到。这在Michael Kerrisk的书中的“信号”章节中有解释。然而,信号只是Linux特定的实现细节,并不是关于pthread的“通用”答案。 - Damon
感谢关于信号传递的澄清。我已经有一段时间想买那本书了...也许现在是时候了。 - FooF
在没有真正并发的Unicore机器上,似乎取消操作实际上会在目标线程再次开始运行(即“任务被调度”时)立即开始显现 - 假设启用了异步取消模式。 - FooF

0
如果您需要猜测异步取消的发生时间,那么您正在做一些非常错误的事情。
  1. 遵循标准:故意创建或允许存在依赖于平台假设(单核心、特定实现等)的代码,会导致你自食其果。如果可能的话,遵循标准通常是更好的选择(并在不可能时清楚地记录)。名称PTHREAD_CANCEL_ASYNCHROUNOUS本身就暗示了异步的含义,这与立即或几乎立即不同。原帖明确指出单核心,但为什么要允许存在那些在真正的并行机器(多个核心或CPU)上以非确定性方式崩溃的代码呢?在这种情况下,要保证立即性几乎是不可能的(这需要停止其他核心运行或等待上下文切换或其他可怕的黑客手段,而你的操作系统/CPU不会支持支持你的非传统愿望)。 异步线程取消模式并不适用于保证立即取消线程。因此,即使它能够工作,以这种方式使用它们是一种可怕的混淆。

  2. 异步安全性:如果你关心异步取消的机制,那么这引起了对问题线程的怀疑(由于缺乏独立性)可能不是纯计算或以异步取消安全方式编写的。

    POSIX仅将三个函数指定为异步取消安全:pthread_cancel(3)pthread_setcancelstate(3)pthread_setcancelmode(3) - 参见IEEE Std 1003.1, 2013 Edition, 2.9.5。这种取消模式只适用于纯计算任务,不调用(除了纯计算之外的)库函数;如果线程设置为在默认延迟取消模式下运行,则此类代码将不提供取消点。因此,定义这种模式的理由

    通过在关键部分禁用取消,可以编写异步取消安全代码。但是,库编写者(包括POSIX库实现者)通常不应关心异步安全性,原因是遵循一般约定、避免复杂性甚至避免性能开销。因为库编写者不应关心,所以你永远不应该期望异步安全性,除非明确说明。

    如果你的代码不是异步安全的(例如调用其他库,包括临时禁用取消或更改取消模式的POSIX/标准C库),并且发生异步取消,你可能会泄漏资源(内存等),留下不一致的状态和锁定互斥量死锁其他线程,并引发许多其他目前可以想象和不可想象的问题。(如果你在编写C++代码,则似乎由于POSIX线程取消与异常处理的密切关联,你将需要处理其他问题。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接