C++ mutex in C++ Threading

系列文章目录

点击直达——文章总目录


Overview


1.mutex in C++ Threading

在 C++ 的多线程编程中,mutex(互斥锁)是一种同步原语,用于保护共享数据不被多个线程同时访问,从而避免竞态条件和数据损坏。C++11 引入了标准库中的线程支持,包括多种类型的互斥锁。

1.1.主要的互斥锁类型

  1. std::mutex
    最基本的互斥锁,用于保护共享数据。它不能被递归锁定。

  2. std::recursive_mutex
    可以被同一个线程多次锁定,这在递归函数需要锁定互斥体时非常有用。

  3. std::timed_mutex
    std::mutex 类似,但它允许尝试锁定操作在超时后失败,而非无限期等待。

  4. std::recursive_timed_mutex
    结合了 std::recursive_mutexstd::timed_mutex 的特性,可以递归锁定,并且支持超时。

1.2.如何使用 std::mutex

锁定互斥锁

  • std::lock_guard:创建时自动锁定,析构时自动释放,用于保护作用域内的代码块。
  • std::unique_lock:更灵活的锁,可以手动锁定和解锁,支持延迟锁定和重复锁定,适用于需要条件变量的场景。

示例代码

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 全局互斥锁
int shared_data = 0;

void increment() {
    std::lock_guard<std::mutex> lock(mtx); // 锁定互斥锁
    ++shared_data; // 安全地修改共享数据
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Shared data: " << shared_data << std::endl; // 输出 2
    return 0;
}

1.3.注意事项

  • 死锁:确保在所有情况下都能避免死锁,例如,总是以相同的顺序获取多个互斥锁。
  • 递归锁定:只有 std::recursive_mutex 可以递归锁定。
  • 异常安全:使用 std::lock_guardstd::unique_lock 可以保证即使在抛出异常时互斥锁也能被释放。
  • 优先级反转:长时间持有锁的线程可能会阻塞高优先级线程,导致优先级反转问题。
  • 资源争用:如果多个线程频繁争用同一互斥锁,可能会导致性能瓶颈。

1.4.使用 std::unique_lock 的高级用法

条件变量

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; }); // 等待条件变量
    std::cout << "Thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true;
    lock.unlock();
    cv.notify_all();
}

int main() {
    std::thread threads[10];
    for (int i = 0; i < 10; ++i) {
        threads[i] = std::thread(print_id, i);
    }
    std::cout << "10 threads ready to race...\n";
    go();
    for (auto& th : threads) {
        th.join();
    }
    return 0;
}

在这个示例中,std::unique_lock 用于条件变量 cv,允许线程在 ready 条件成立之前挂起等待。

正确使用互斥锁对于编写安全且高效的多线程程序至关重要。

2.如何使用 std::recursive_mutex 来避免死锁问题?

std::recursive_mutex 是 C++ 标准库中的一个互斥锁类型,它允许同一个线程可以多次获得同一个锁,而不会产生死锁。这在递归函数或者需要在同一个线程中多次获取锁的情况下非常有用。

2.1.使用 std::recursive_mutex 的基本步骤

  1. 包含头文件
    包含 mutex 头文件。

  2. 声明 std::recursive_mutex
    创建一个 std::recursive_mutex 对象。

  3. 锁定和解锁
    使用 std::lock_guardstd::unique_lock 来管理锁的生命周期。

2.2.示例代码

#include <iostream>
#include <thread>
#include <mutex>
#include <recursive_mutex>

std::recursive_mutex rw_mutex;

void recursiveFunction(int depth) {
    std::lock_guard<std::recursive_mutex> lock(rw_mutex);
    std::cout << "Locked at depth " << depth << std::endl;
    if (depth > 0) {
        recursiveFunction(depth - 1);
    }
    std::cout << "Unlocked at depth " << depth << std::endl;
}

int main() {
    std::thread t1(recursiveFunction, 2);
    std::thread t2(recursiveFunction, 2);
    t1.join();
    t2.join();
    return 0;
}

2.3.如何使用 std::recursive_mutex 避免死锁

  1. 避免嵌套锁
    使用 std::recursive_mutex 允许你在同一个线程中多次锁定同一个互斥锁,从而避免死锁。

  2. 一致的锁顺序
    确保所有线程获取多个锁时都按照相同的顺序来获取,这样可以避免循环等待条件。

  3. 使用 try_locktry_lock_for
    使用 try_locktry_lock_for 尝试获取锁,如果获取失败则可以释放已持有的锁,避免死锁。

  4. 使用超时机制
    使用 std::unique_lockstd::condition_variable 的超时等待功能,如果超时则释放锁。

  5. 避免在持有锁时调用未知的库函数
    确保在持有锁时不调用可能会获取其他锁的库函数,因为这可能导致不可预见的锁依赖关系。

2.4.示例:使用 try_lock 避免死锁

#include <iostream>
#include <thread>
#include <mutex>
#include <recursive_mutex>
#include <chrono>

std::recursive_mutex rw_mutex1, rw_mutex2;

void lock_both(std::recursive_mutex& m1, std::recursive_mutex& m2) {
    std::lock_guard<std::recursive_mutex> lock1(m1);
    std::lock_guard<std::recursive_mutex> lock2(m2);
    // 安全地访问共享资源
}

void threadFunction(std::recursive_mutex& m1, std::recursive_mutex& m2) {
    if (m1.try_lock()) {
        if (m2.try_lock()) {
            lock_both(m1, m2);
            m2.unlock();
        } else {
            // 释放 m1 并重试
            m1.unlock();
        }
    }
}

int main() {
    std::recursive_mutex rw_mutex1, rw_mutex2;
    std::thread t1(threadFunction, std::ref(rw_mutex1), std::ref(rw_mutex2));
    std::thread t2(threadFunction, std::ref(rw_mutex2), std::ref(rw_mutex1));
    t1.join();
    t2.join();
    return 0;
}

在这个示例中,try_lock 用于尝试获取锁,如果获取失败,则释放已持有的锁,避免死锁。

通过这些方法,std::recursive_mutex 可以帮助你在复杂的多线程环境中更安全地管理锁,从而避免死锁问题。

3.在C++中,除了互斥锁,还有哪些同步机制可以保护共享数据?

在 C++ 中,除了互斥锁(std::mutex),还有多种同步机制可以保护共享数据,这些机制提供了不同的功能和使用场景:

  1. 原子操作(std::atomic
    提供了一种无锁的线程安全编程方式,适用于对单个数据点进行简单的读写操作。

    std::atomic<int> atomic_var(0);
    atomic_var.fetch_add(1, std::memory_order_relaxed);
    
  2. 读写锁(std::shared_mutex
    允许多个线程同时读取共享数据,但写入时需要独占访问。

    std::shared_mutex rw_mutex;
    std::shared_lock<std::shared_mutex> lock(rw_mutex);
    
  3. 条件变量(std::condition_variable
    用于线程间的同步,允许一个或多个线程挂起,直到被其他线程唤醒。

    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;
    cv.wait(mtx, [&](