pthread_cancel() 是否有可能终止另一个意外的线程?

Is it possible that pthread_cancel() terminates another unintended thread?

我的问题与this one类似。但是看完它的所有答案,我仍然不知道pthread_cancel()能得到什么样的安全保障。所以我想问一个更具体的问题:

假设 pthread_cancel() 在名为 my_thread 的 pthread_t 变量上调用,是否有可能到 pthread_cancel(my_thread ) 被执行时,对应于 my_thread 的实际线程已经以某种方式终止,内核为另一个新创建的线程回收了 my_thread 的值,这样通过执行 pthread_cancel(my_thread), 另一个意外的线程被杀死了?

在分离或加入线程之前,该值不能是 "recycled"。只要你没有做任何这些事情,调用 pthread_cancel 是安全的,即使线程已经终止。

问题是关于 pthread_cancel() 的竞争条件。 POSIX 要求该函数在使用该术语的特定的、有限的意义上是线程安全的,但这并不能真正说明手头的问题。关键细节在 XSH 2.9.2 中指定,正如 @R.. 在前面的评论中观察到的那样。特别是:

The lifetime of a thread ID ends after the thread terminates if it was created with the detachstate attribute set to PTHREAD_CREATE_DETACHED or if pthread_detach() or pthread_join() has been called for that thread. A conforming implementation is free to reuse a thread ID after its lifetime has ended. If an application attempts to use a thread ID whose lifetime has ended, the behavior is undefined.

因此,允许应用程序重新使用生命周期已结束的线程 ID,但这实际上是一个附带问题,因为如果您尝试使用陈旧的线程 ID,则行为是未定义的,无论 ID 是否已被重新使用.当然,在所描述的案例中可能发生的无数 UB 表现形式之一确实是取消了与您打算取消的线程不同的线程,无论线程 ID 是否已被重用。

线程 ID 的生命周期在它标识的线程终止时结束(如果该线程是分离创建的),或者当它被传递给 pthread_detachpthread_join(如果线程是可连接创建的)。完全有可能在 pthread_cancel 的执行之间进行竞争。如果创建的线程是可连接的,那么您总共至少需要三个线程,但如果创建的线程是分离的,那么除了调用 pthread_cancel 的线程和一个被取消的单独线程之外,您不需要任何其他线程。无论哪种方式 pthread_cancel 都是有风险的。

您链接的问题的公认答案充其量是误导性的,但@DavidSchwartz 对此的评论更有用,即使我认为它没有准确反映每个细节的规范。我会这样说:

  1. 如果以下情况之一成立,则使用 pthread_cancel 取消线程是安全的:

    • 创建的线程是可连接的,并且可以肯定的是它在 pthread_cancel 调用完成之前不能被分离或连接,或者
    • 线程是分离创建的,可以肯定它不会终止,也不会在 pthread_cancel 调用完成。
  2. 尝试取消安全(它有 UB 的风险)

    • 创建的线程可通过 pthread_create 提供的线程 ID 创建,如果在 pthread_cancel 调用完成之前可以分离或加入该线程, 或

    • 创建的线程分离,如果该线程有可能终止或在 pthread_cancel 调用之前调用 pthread_joinpthread_detach完成。

  3. 目前尚不清楚通过分离后从 pthread_self() 获得的线程 ID 取消创建为可连接但后来分离的线程是否安全,如果确定两者都不是pthread_joinpthread_detach 都不能在 `pthread_cancel 完成之前对该线程 ID 调用。*


*可以将规范解释为暗示在那些情况下,pthread_self returns 一个线程 ID 的生命周期已经结束,因此取消肯定会产生 UB。但是至少有几个不同的相反解释,并且在任何一种情况下,都没有定义来自 pthread_self 的线程 ID 的生命周期在程序结束之前结束的条件,从而可以安全地取消随时通过该 ID 进行讨论。