C++中互斥量、锁有什么用?

2024-01-02 12:35:51

创建一个C++线程需要传入几个参数?
如何理解和使用C++线程循环
C++ 类 函数 变量 进程 线程
C++关于锁和互斥量你真的理解了吗
C++ 代码中如何使用互斥锁std::mutex和独占锁std::unique_lock

互斥量

在 C++ 中,互斥量(Mutex,即 Mutual Exclusion)是并发编程中的一种基本工具,用于控制多个线程对共享资源的访问,以防止数据竞争和相关的未定义行为。互斥量提供了一种机制,允许一个线程一次只锁定一次资源,确保在该线程释放互斥量之前,其他线程不能访问被保护的资源。

如何理解互斥量

互斥量可以被视为一种锁,用来保护共享数据,确保在任何时候只有一个线程可以访问这些数据。当一个线程需要访问被互斥量保护的资源时,它必须先锁定(acquire)这个互斥量。如果互斥量已被另一个线程锁定,该线程将被阻塞,直到互斥量被释放(unlock)。这防止了多个线程同时读写同一数据,从而防止了数据损坏和不一致性。

C++ 中互斥量的用法

C++ 标准库提供了 <mutex> 头文件,其中包含用于管理互斥量的类。最常用的类是 std::mutex。以下是 std::mutex 的基本用法:

基本锁定和解锁
#include <iostream>
#include <mutex>

std::mutex mtx; // 定义一个互斥量

void function() {
    mtx.lock();   // 锁定互斥量
    // ... 执行需要互斥的操作 ...
    mtx.unlock(); // 解锁互斥量
}
#include <iostream>
#include <mutex>

std::mutex mtx; // 全局互斥锁

void print_function(const std::string& message) {
    mtx.lock(); // 手动锁定
    std::cout << message << std::endl;
    mtx.unlock(); // 手动解锁
}

int main() {
    print_function("Hello from main function");

    return 0;
}
  • main 函数直接调用 print_function,而不是创建新线程来调用它。
  • print_function 依然使用 mtx.lock()mtx.unlock() 来手动管理互斥锁,尽管在单线程环境中这实际上是不必要的。

请注意,在单线程程序中使用互斥锁通常是没有必要的,因为没有并发访问共享资源的风险。此示例仅用于说明如何单独使用 std::mutex。在实际应用中,您应该根据程序的并发需求来决定是否使用互斥锁。

使用 std::lock_guard 自动管理锁

为了避免忘记解锁或在异常发生时遗漏解锁,可以使用 std::lock_guard,它提供了一种自动管理锁的方法(RAII,资源获取即初始化)。

void safe_function() {
    std::lock_guard<std::mutex> lock(mtx);
    // ... 执行需要互斥的操作 ...
} // `lock_guard` 在作用域结束时自动释放互斥量
使用 std::unique_lock 获得更多控制

std::unique_lock<std::mutex> 提供了比 std::lock_guard 更多的灵活性,允许你延迟锁定,重复锁定和解锁,以及移动锁。

void flexible_function() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // ... 这里互斥量还未锁定 ...
    lock.lock(); // 手动锁定
    // ... 执行需要互斥的操作 ...
    lock.unlock(); // 可以手动解锁
    // ... 可以在不同的时间点再次锁定 ...
}

注意事项

  • 避免死锁:确保在所有路径上,无论是正常还是异常,锁都能被释放。std::lock_guardstd::unique_lock 在这方面非常有用。
  • 锁的粒度:锁应该保护的是访问共享资源的最小必要代码区域,以避免不必要的性能损失。
  • 不要过度使用互斥量:在不需要保护共享资源的地方不要使用互斥量,以免引入不必要的性能开销和复杂性。

正确地使用互斥量可以确保多线程程序的正确性和稳定性,但需要谨慎使用以避免常见的陷阱,如死锁和性能瓶颈。

几种不同类型的锁

在 C++ 中,除了 std::mutex,标准库提供了几种不同类型的锁,以适应不同的并发和同步需求。下面是一些常见的锁类型及其简要说明:

  1. std::mutex:

    • 最基本的互斥锁,用于保护共享数据,防止多个线程同时访问。std::mutex 提供了基本的锁定和解锁功能,但没有递归锁定的能力。
  2. std::recursive_mutex:

    • 递归互斥锁,允许同一线程多次获取锁。如果一个线程已经持有该锁,它可以再次锁定而不会产生死锁。这对于递归函数或可重入函数很有用。
  3. std::timed_mutex:

    • 带有超时功能的互斥锁。它允许尝试锁定一个互斥锁直到指定的时间点或者经过指定的时间段。如果在指定时间内无法获得锁,则返回。
  4. std::recursive_timed_mutex:

    • 结合了 std::recursive_mutexstd::timed_mutex 的特性。它是一个可以由同一线程多次锁定的互斥锁,同时提供了带超时的锁定尝试。
  5. std::shared_mutex (C++17):

    • 允许多个线程同时读取共享数据,但写操作是互斥的。这对于读多写少的场景非常有用。
  6. std::shared_timed_mutex (C++14):

    • std::shared_mutex 的一个变体,提供了超时功能。它允许多个线程同时进行读取操作,但写入操作将独占锁定。
  7. std::lock_guard:

    • 是一个作用域锁,它在构造时自动锁定给定的互斥锁,并在析构时自动解锁。它提供了一种方便的方式来确保在作用域结束时释放互斥锁。
  8. std::unique_lock:

    • std::lock_guard 更灵活的作用域锁。它不仅允许在构造时延迟锁定,还可以在生命周期内多次锁定和解锁互斥锁。

这些锁的选择和使用取决于特定的应用场景和性能要求。例如,对于简单的互斥需求,std::mutexstd::lock_guard 通常就足够了。而对于需要更精细控制的场景,如需要超时或递归锁定功能,你可能需要选择 std::timed_mutexstd::recursive_mutex 等更高级的锁类型。

文章来源:https://blog.csdn.net/qq_21950671/article/details/135336986
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。