<mutex> 是 C++ 标准库中用于线程同步的核心头文件,提供了多种互斥量类型及其管理工具。利用这些工具,你可以保护共享数据,防止多个线程同时访问同一资源时引发数据竞争或不一致问题。下面将介绍 <mutex> 的主要组件和常见使用方式。


目录

  1. <mutex> 头文件简介
  2. 互斥量基础
  3. RAII 风格的锁管理
  4. 多锁与死锁预防
  5. 常见注意事项
  6. 结论

1. <mutex> 头文件简介

C++ 的 <mutex> 提供了线程同步所需的各种互斥量(mutex)和相关工具。通过这些工具,可以防止多个线程同时操作共享资源,从而保证程序的正确性和数据一致性。要使用这些功能,只需在程序中包含该头文件:

#include <mutex>


2. 互斥量基础

2.1 std::mutex

  • 功能std::mutex 是最基本的互斥量,用于保护临界区,确保同一时刻只有一个线程访问共享数据。
  • 常用方法
    • lock():上锁,如果互斥量已被锁住,则当前线程阻塞。
    • unlock():解锁,释放互斥量。
    • try_lock():尝试上锁,如果无法立即获得锁则返回 false,否则返回 true

示例:

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

std::mutex mtx;
int counter = 0;

void increment() {
    mtx.lock();
    ++counter;
    std::cout << "Counter: " << counter << std::endl;
    mtx.unlock();
}

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

2.2 std::recursive_mutex

  • 功能:允许同一线程对同一互斥量进行多次加锁而不会引发死锁,适用于递归调用或嵌套调用场景。
  • 使用注意:每次调用 lock() 后必须对应一次 unlock()

示例:

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

std::recursive_mutex rec_mtx;
int value = 0;

void recursiveFunction(int n) {
    if(n <= 0)
        return;
    rec_mtx.lock();
    ++value;
    std::cout << "Value: " << value << " at recursion level: " << n << std::endl;
    recursiveFunction(n - 1);
    rec_mtx.unlock();
}

int main() {
    std::thread t(recursiveFunction, 3);
    t.join();
    return 0;
}

2.3 定时互斥量

  • 功能:例如 std::timed_mutexstd::recursive_timed_mutex 提供带超时功能的加锁接口,如 try_lock_fortry_lock_until,允许在指定时间内尝试加锁。

3. RAII 风格的锁管理

直接调用 lock()unlock() 虽然简单,但容易因为异常或遗漏解锁而导致死锁。C++ 提供了 RAII(资源获取即初始化)风格的锁管理类,它们能自动在离开作用域时释放锁,从而大大提高代码安全性。

3.1 std::lock_guard

  • 特点:构造时自动上锁,析构时自动解锁。适用于简单场景。

示例:

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

std::mutex mtx;
int counter = 0;

void safeIncrement() {
    std::lock_guard<std::mutex> lock(mtx);
    ++counter;
    std::cout << "Safe counter: " << counter << std::endl;
}

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

3.2 std::unique_lock

  • 特点:比 std::lock_guard 更灵活,支持延迟加锁、提前解锁和条件变量协作等功能。

示例:

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

std::mutex mtx;
int counter = 0;

void flexibleIncrement() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 可以在这里执行其他操作,再决定何时加锁
    lock.lock();
    ++counter;
    std::cout << "Flexible counter: " << counter << std::endl;
    // 可选:lock.unlock();  提前释放锁
}

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


4. 多锁与死锁预防

在涉及多个共享资源的情况下,多个线程同时请求多个互斥量可能会导致死锁。为避免此类问题,你可以:

  • 一致性加锁顺序:所有线程在获取多个锁时,按照相同的顺序获取。
  • 使用 std::lock():该函数可以一次性锁定多个互斥量,并避免死锁。

示例:

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

std::mutex mtx1, mtx2;

void taskA() {
    std::lock(mtx1, mtx2);
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    std::cout << "Task A completed." << std::endl;
}

void taskB() {
    std::lock(mtx1, mtx2);
    std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
    std::cout << "Task B completed." << std::endl;
}

int main() {
    std::thread t1(taskA);
    std::thread t2(taskB);
    
    t1.join();
    t2.join();
    return 0;
}


5. 常见注意事项

  • 确保总是解锁:尽量使用 RAII 风格的锁类(如 std::lock_guardstd::unique_lock)自动管理锁的生命周期。
  • 缩小临界区:将持锁的代码范围最小化,避免在锁内执行耗时操作。
  • 选择合适的互斥量:根据需求选择标准互斥量、递归互斥量或定时互斥量,以平衡安全性和性能。

6. 结论

<mutex> 为 C++ 多线程编程提供了重要的同步机制。通过合理使用互斥量和 RAII 风格的锁管理工具,你可以防止数据竞争、减少死锁风险,从而编写出线程安全且高效的并发程序。掌握这些工具是编写可靠多线程代码的关键。

推荐阅读: