C++ 中线程安全和可重入代码的区别

2025 年 5 月 15 日 | 阅读 4 分钟

在 C++ 中,线程安全可重入是处理并发编程时经常出现的两个关键概念。虽然它们相关,但并不相同,理解它们之间的区别对于在多线程环境中编写安全高效的代码至关重要。

线程安全

如果一个函数或一段代码可以被多个线程同时安全地调用而不会导致不正确的行为,那么它就被认为是线程安全的。它通常需要适当的同步机制(如锁或互斥锁)来防止竞争条件,确保共享资源以受控的方式被访问。

可重入代码

如果一个函数在执行中途被中断,然后可以安全地被再次调用(即使是来自同一个线程),而不会影响前一次执行的正确性,那么这个函数就是可重入的。可重入性通常意味着该函数不依赖于共享的、可变的全局状态,也不使用静态或堆分配的变量,这些变量在两次调用之间会保持不一致的状态。

主要关键区别

  • 线程安全确保在多线程环境中的正确性。
  • 可重入性确保一个函数可以被中断并安全地重新进入,即使在同一个线程内。

示例类型

  • 非线程安全,非可重入
  • 线程安全,非可重入
  • 非线程安全,可重入
  • 线程安全,可重入

1. 非线程安全,非可重入

这是最不安全的情况。函数使用共享的全局或静态变量而没有保护,不能被安全地中断或被多个线程调用。

  • 线程安全: 非线程安全,因为两个线程可能同时更新 global_counter,导致竞争条件。
  • 可重入性: 非可重入,因为如果被中断并重新进入,global_counter 在两次调用之间会处于不一致的状态。

2. 线程安全,非可重入

在这种情况下,函数通过使用互斥锁或锁来确保线程安全,但它不是可重入的,因为如果函数被中断,锁无法被重新获取。

  • 线程安全: 线程安全,因为锁确保一次只有一个线程可以更新 global_counter。
  • 可重入性: 非可重入,因为如果函数被中断并从同一个线程再次调用,它将再次尝试获取锁,导致死锁。

3. 非线程安全,可重入

这个函数不使用共享状态,因此可以被中断并安全地重新进入,但它不是线程安全的,因为它仍然涉及未同步的全局或共享变量。

  • 线程安全: 非线程安全,因为多个线程可能同时尝试修改计数器,导致竞争条件。
  • 可重入性: 可重入,因为函数不依赖于任何外部因素,使其在同一线程内被中断并再次调用变得不安全(即使它使用了静态变量)。

4. 线程安全,可重入

这是理想的情况。函数不依赖任何共享的、可变的状态,并确保对多线程的正确处理。

线程安全: 线程安全,因为不涉及共享状态,每次调用都独立于其他线程。

可重入性: 可重入,因为函数不依赖任何外部状态,不会因为被中断或重新进入而产生问题。

线程安全与可重入代码的关键区别

以下是几个关键区别

方面非线程安全,非可重入线程安全,非可重入非线程安全,可重入线程安全,可重入
共享状态使用无保护的共享/全局状态保护共享状态(例如,使用互斥锁)无共享状态,但使用静态/全局变量无共享状态,无静态/全局变量
线程安全非线程安全;可能发生竞争条件线程安全;使用锁或同步非线程安全;共享数据无同步线程安全;无需同步
可重入性非可重入;被中断的调用可能导致状态不一致非可重入;如果被中断,锁可能导致死锁可重入;可以被中断而无问题可重入;对中断和重入都是安全的
全局变量的使用示例使用未受保护的全局/静态变量对全局/静态变量使用锁使用静态变量但无同步不使用全局/静态变量
处理多线程对于多线程不安全对于多线程安全对于多线程不安全对于多线程安全
处理中断(重入)被中断或重入时失败因锁而中断时失败(有死锁风险)能处理中断,但对并发线程不安全对中断和可重入调用都是安全的
示例函数global_counter++受互斥锁保护的 global_counter++使用静态变量但无锁的函数无状态函数,如 a + b