C++ 中 std::lock_guard 与 std::unique_lock 的区别2025年3月18日 | 阅读 10 分钟 简单地基于 RAII 的互斥锁 std::lock_guard 在构造时锁定互斥锁,并在不提供用户控制的情况下在销毁时释放它。另一方面,std::unique_lock 函数更灵活,因为它允许所有权转移、定时锁定、手动解锁和延迟锁定。对于基本的锁定控制,请使用 std::lock_guard 函数;对于高级锁定控制,请使用 std::unique_lock 函数。 什么是 std::lock_guard?确保在作用域内正确锁定和解锁互斥锁的快速简便方法是使用 C++ 模板类 std::lock_guard。它包含在头文件中,是 C++ 标准库 的一部分。通过遵循 RAII(资源获取即初始化)原则,std::lock_guard 确保在 lock_guard 对象创建时自动锁定互斥锁,并在其销毁或超出作用域时自动解锁。 std::lock_guard 的主要特性C++ 中 std::lock_guard 方法的几个特性如下: - 自动锁定: 在创建 std::lock_guard 对象时,会在构造函数中提供的互斥锁上进行锁定。由于开发人员不必显式调用 lock 或 unlock,因此这种自动锁定简化了互斥锁的管理。
- 自动解锁: 在包含 std::lock_guard 对象的函数或块结束时,会调用析构函数,从而自动解锁互斥锁。此功能可确保异常安全,这意味着即使抛出异常,互斥锁也会被正确释放。
- 简单性: 该类专为简单用例而设计,在这些用例中,必须在作用域代码块的持续时间内锁定互斥锁。它不兼容定时锁定或手动解锁等高级功能。
- 资源获取即初始化 (RAII) 原则: 为了提供系统资源(互斥锁)的基于作用域的管理,std::lock_guard 函数确保在对象的持续时间内锁定互斥锁,并在销毁时自动解锁。
- 无高级锁定支持: 与 std::unique_lock 相比,std::lock_guard 不支持条件锁定或定时锁定、延迟锁定和锁所有权转移等高级功能。它专为基本、自动的互斥锁管理应用程序而设计。
- 异常安全性: std::lock_guard 提供强大的异常保护,因为它在离开作用域时会自动解锁互斥锁。为了避免死锁,即使在抛出异常的情况下,也能正确释放互斥锁。
std::lock_guard 的优点C++ 中 std::lock_guard 方法的几个优点如下: - 简单性: std::lock_guard 函数在 C++ 中非常简单。互斥锁在创建时自动锁定,在销毁时(例如,当它退出作用域时)自动解锁。这使得代码更简单,并降低了人为错误的风险,例如忘记处理异常或解锁互斥锁。
- 基于 RAII 的锁定: 基于 RAII 原则,std::lock_guard 在 std::lock_guard 对象销毁时自动释放互斥锁,就像 std::unique_lock 一样。它通过确保异常安全和互斥锁始终解锁来避免潜在的死锁和资源泄漏。
- 无额外开销: std::lock_guard 没有额外的开销,因为它没有 std::unique_lock 的高级功能,例如延迟锁定或手动解锁。它是一个非常简单的抽象,不会增加不必要的复杂性或性能成本,并且正好完成了锁定和解锁互斥锁的必要工作。
- 适用于简单场景: 在只需要锁定互斥锁直到作用域(例如函数或代码块)结束的场合,std::lock_guard 是最佳选择。使用 std::lock_guard 可以使代码可读且简单,无需进行条件锁定或延迟锁定。
std::lock_guard 的缺点C++ 中 std::lock_guard 方法的几个缺点如下: - 在复杂锁定场景中效率低下: 在复杂的锁定情况下,例如需要同时锁定多个互斥锁,std::lock_guard 不是最佳选择。例如,在容易发生死锁并需要锁定多个互斥锁的情况下,您可能需要使用 std::scoped_lock 或 std::lock(),它们都旨在原子地处理多个锁。std::lock_guard 一次只能处理一个互斥锁。
- 无延迟锁定: std::lock_guard 要求在构造时立即锁定互斥锁,这在您希望延迟锁定直到满足特定条件或事件发生的情况下可能很受限制。在需要对锁定进行更多控制的情况下,这种不灵活的方法可能会导致代码效率低下。
- 不适用于条件变量: 使用 std::condition_variable 时不能使用 std::lock_guard,因为它需要一个可以解锁然后根据条件满足时重新锁定的锁对象。在这种情况下,需要 std::unique_lock,因为它允许在等待条件时解锁互斥锁并在等待后重新获取它。
- 不支持锁状态查询: std::lock_guard 不提供检查互斥锁是否已锁定的方法。在较简单的用例中通常不成问题,但在更高级的同步场景中,能够查询锁定状态(例如,通过 std::unique_lock 中的 owns_lock())可能很有用。
什么是 std::unique_lock?C++ 模板类 std::unique_lock 灵活且强大。它控制互斥锁的锁定和解锁。它包含在头文件中,是 C++ 标准库 的一部分。虽然 std::lock_guard 会自动锁定和解锁互斥锁,但 std::unique_lock 提供了对互斥锁管理更多的自由和控制。它允许在各种线程之间转移锁所有权、定时锁定、手动解锁和定时锁定。 条件锁定以及在必要时显式锁定或解锁互斥锁的能力是 std::unique_lock 提供的两个功能,用于促进多线程系统中的复杂锁定场景。 此外,它符合 RAII(资源获取即初始化)原则,确保除非预先手动解锁互斥锁,否则在 unique_lock 对象退出作用域时它将自动解锁。 std::unique_lock 的主要特性C++ 中 std::unique_lock 方法的几个特性如下: - 手动锁定和解锁: 与 std::lock_guard 相比,std::unique_lock 允许用户显式控制互斥锁的锁定和解锁。稍后在代码中,可以通过显式调用 lock 来延迟锁定。
- 延迟锁定: 您可以通过使用 std::unique_lock 创建一个对象而不立即锁定互斥锁。这在可能需要延迟锁定直到满足特定要求的情况下很有用。
- 条件锁定和定时锁定: try_lock() 方法尝试在不阻塞的情况下获取锁,提供条件锁定。此外,线程可以通过使用 try_lock_for() 和 try_lock_until() 方法来等待指定时间的锁,以启用定时锁定。
- 所有权转移: Std::unique_lock 可以使用移动语义将锁的所有权分配给另一个 Std::unique_lock 对象。当需要在程序其他部分之间转移锁时,这非常有用。
- 定时和条件锁定支持: try lock for() 和 try lock until() 是支持的基于时间的锁定技术,它们是 std::unique_lock 的示例。您可以使用这些方法尝试在特定时间内或直到特定时间点获取锁。
- 互斥锁状态查询: 您可以使用 owns_lock() 函数来确定互斥锁当前是否被 std::unique_lock 拥有。当存在延迟或定时锁定场景且不清楚是否成功获取锁时,这非常有用。
std::unique_lock 的优点C++ 中 std::unique_lock 方法的几个优点如下: - 锁定的灵活性: std::unique_lock 提供了对何时以及如何锁定互斥锁的灵活控制。与在构造时锁定互斥锁的 std::lock_guard 相比,std::unique_lock 允许延迟锁定,从而可以推迟锁定直到真正需要。这在有操作必须在获取锁之前完成的情况下很有用。
- 手动锁定和解锁控制: 它允许手动锁定和解锁互斥锁。显式的 lock() 和 unlock() 调用允许更复杂的控制流,例如暂时解锁互斥锁以避免死锁或减少关键部分中的锁争用。
- 支持定时锁定: std::unique_lock 中的 try lock for() 和 try lock until() 方法支持定时锁定。这有助于避免在不希望无限期等待的情况下发生死锁和忙等待,因为它允许线程尝试在特定时间段内或直到特定时间点获取锁。
- 可转移所有权: 移动语义允许锁的所有权在 std::unique_lock 的不同实例之间移动。它通过能够跨不同函数或线程传递锁,为应用程序处理和共享锁提供了更大的自由度。
- 自动资源管理: std::unique_lock 与其他基于 RAII 的构造一样,确保当锁对象退出作用域时,即使发生异常,互斥锁也会立即解锁。它减少了由于错误而导致互斥锁保持锁定的可能性。
- 死锁避免: std::unique_lock 通过提供尝试在特定时间内锁定的选项以及手动解锁的机会,有助于死锁避免技术。它有助于处理不同的线程可能以不同顺序获取多个锁,这可能导致死锁的情况。
std::unique_lock 的缺点C++ 中 std::unique_lock 方法的几个缺点如下: - 增加的开销: 与 std::lock_guard 相比,std::unique_lock 更高级,这导致了一些性能开销。这主要是因为它允许附加功能,例如所有权转移、定时锁定和延迟锁定。与更简单的锁相比,开销包括更大的代码、更多的检查,以及偶尔稍慢的执行速度。
- 不适合简单用例: 在只需要简单的基于 RAII 的锁定而不需要手动或延迟锁定等场景中,std::unique_lock 可能是不必要的。在某些情况下,std::lock_guard 更用户友好、简单且有效。
- 潜在的误用: 尽管 std::unique_lock 提供了灵活性,但仍有可能被误用。当在没有先手动锁定互斥锁的情况下使用延迟锁定时,可能会发生运行时错误。同样,不正确的手动解锁可能导致死锁或竞争情况。
- 仅移动语义: std::unique_lock 的仅移动语义使其不可复制。虽然这是防止两个锁管理同一互斥锁所必需的,但它也意味着 std::unique_lock 对象不能按值传递,这可能会限制它们在某些设计中的应用,特别是与 std::lock_guard 等更简单的锁相比,后者更容易传递。
C++ 中 std::lock_guard 和 std::unique_lock 的主要区别 C++ 中的 std::lock_guard() 和 std::unique_lock() 之间有几个主要区别。一些主要区别如下: 特性 | std::lock_guard | std::unique_lock |
---|
定义 | std::lock_guard 是一种简单而有效的方法,用于在单个作用域中管理互斥锁。为了确保在简单场景中的异常安全并避免死锁,它会在创建时自动锁定互斥锁,并在销毁时解锁。 | std::unique_lock 提供了更通用的锁定方法,支持高级互斥锁管理。它适用于需要更多通用性的复杂场景,因为它提供了所有权转移、定时锁定和延迟锁定。 | 复杂度 | 对于不需要额外功能的简单关键部分,std::lock_guard 函数因其最小的开销和简单的接口而成为一个绝佳的选择。其设计促进了代码的可读性和简洁性。 | 相比之下,std::unique_lock 的附加功能增加了开销并使其更加复杂。由于更高级别的复杂性,开发人员现在可以修改锁定行为以满足特定需求,但代价是需要对所需机制有更深入的理解。 | 锁定行为 | 在构造 std::lock_guard 时,会自动锁定关联的互斥锁。当 lock_guard 对象进入其作用域时,它会被释放。在异常情况下,它确保互斥锁始终被正确释放。 | std::unique_lock 允许开发人员在其作用域内多次锁定和解锁互斥锁,从而为他们提供了对锁定和解锁的更多控制。当需要暂时释放锁或必须根据特定条件修改锁定行为时,这尤其有用。 | 灵活性 | 由于 std::lock_guard 禁止手动控制锁定和解锁,因此它非常适合具有可预测锁定模式的简单场景。当需要锁定互斥锁并在其生命周期内保持它时,它的效果最好。 | 然而,std::unique_lock 提供了很大的灵活性,因为它允许手动控制锁定和解锁过程。在更复杂的应用程序中,例如条件锁定,其中锁仅在满足特定要求或在特定事件期间获取时,这种灵活性非常有用。 | 延迟锁定 | 由于 std::lock_guard 不支持延迟锁定,因此无法在不立即锁定关联互斥锁的情况下进行构造。在需要快速锁定的情况下,这是合适的。 | 因为 std::unique_lock 支持延迟锁定,所以程序员可以在不立即锁定互斥锁的情况下构造一个 unique_lock。在必须将锁定推迟到满足特定要求的情况下,这可能很有用。 | 定时锁定 | 不支持 | 支持。它通过允许在特定时间窗口内获取锁来减少无限期等待的可能性。 | 所有权转移 | 不支持 | 支持。它提供了在线程之间转移锁所有权的能力,提高了涉及多个线程的场景的适应性。 | 理想用途 | 使用单个作用域和简单性进行锁定。非常适合简单场景中短暂的锁。 | 它用于需要详细锁控制的复杂场景,例如需要条件锁定或延迟锁的多线程系统。 | 异常安全 | 它保证在销毁时解锁,提供自动异常保护。 | 销毁时保证解锁,但如果未正确处理更多控制,会增加出错的几率(例如) | 开销 | 最小 | 由于增加了灵活性和功能,略高。 |
结论总之,在 C++ 中处理互斥锁需要使用 std::lock_guard 和 std::unique_lock。虽然由于其易用性和最小的开销,std::lock_guard 在简单的锁定场景中是首选,但 std::unique_lock 在需要高级锁定机制(包括所有权转移以及延迟和定时锁定)的复杂场景中表现出色。对于简单的场景,std::lock_guard 是最佳选择,而 std::unique_lock 则提供了更复杂的并发管理所需的灵活性。两者之间的选择取决于应用程序的特定需求。
|