c++中的mutex同步机制与多线程同步实现

发布于:2025-07-03 ⋅ 阅读:(18) ⋅ 点赞:(0)

C++ 中的 std::mutex 与多线程同步

在多线程编程中,互斥锁(Mutex) 是一种同步机制,用于保护共享资源(如变量、数据结构)免受数据竞争(Data Race)的影响。C++ 标准库中的 std::mutex 提供了基本的互斥功能,常与 std::lock_guardstd::unique_lock 配合使用,以实现线程安全的资源访问。


1. std::mutex 的基本概念

  • 作用:确保在同一时刻只有一个线程可以访问共享资源。
  • 头文件#include <mutex>
  • 常用方法
    • lock():锁定互斥锁,若已被锁定则阻塞等待。
    • unlock():释放互斥锁。
    • try_lock():尝试锁定互斥锁,若失败则立即返回(不阻塞)。

2. 示例:不使用互斥锁导致数据竞争

场景:多个线程对共享计数器进行递增操作。
#include <iostream>
#include <thread>
#include <vector>

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;  // 非原子操作,存在数据竞争
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(increment);
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

输出(每次运行结果可能不同):

Final counter value: 789123  // 错误值(期望 1,000,000)

问题分析

  • ++counter 不是原子操作,多个线程同时修改 counter 导致数据竞争。
  • 结果不可预测,可能远小于预期值。

3. 使用 std::mutexstd::lock_guard 修复数据竞争

改进方案:通过互斥锁保护共享资源。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>  // 引入 mutex

int counter = 0;
std::mutex mtx;  // 定义互斥锁

void increment() {
    for (int i = 0; i < 100000; ++i) {
        mtx.lock();     // 加锁
        ++counter;      // 安全访问共享资源
        mtx.unlock();   // 解锁
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(increment);
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

输出

Final counter value: 1000000

关键点

  • 互斥锁保护临界区mtx.lock()mtx.unlock() 之间的代码为临界区,确保一次只有一个线程执行。
  • 避免死锁:必须成对调用 lock()unlock(),否则可能导致死锁。

4. 使用 std::lock_guard 自动管理锁的生命周期

改进方案:使用 RAII(Resource Acquisition Is Initialization) 模式自动加锁/解锁。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 构造时加锁,析构时自动解锁
        ++counter;  // 安全访问共享资源
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(increment);
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

输出

Final counter value: 1000000

优势

  • 自动解锁lock_guard 在作用域结束时自动调用 unlock(),避免手动管理锁。
  • 异常安全:即使发生异常,lock_guard 也会确保解锁。

5. std::unique_lock:更灵活的锁管理

使用场景:需要延迟锁定、条件变量或动态锁定策略。
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_data = 0;

void access_data() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 不立即加锁
    // 做一些不需要锁的操作...
    lock.lock();  // 显式加锁
    shared_data = 42;
    std::cout << "Data updated: " << shared_data << std::endl;
    // lock 在作用域结束时自动解锁
}

int main() {
    std::thread t(access_data);
    t.join();
    return 0;
}

输出

Data updated: 42

特点

  • 延迟锁定:使用 std::defer_lock 参数延迟加锁。
  • 支持移动:可以将锁的所有权转移到其他线程。
  • 配合条件变量:常用于 std::condition_variable 的等待操作。

6. 互斥锁的最佳实践

原则 说明
保护共享资源 所有对共享资源的访问都必须通过互斥锁保护。
最小化临界区 锁定时间越短越好,避免阻塞其他线程。
避免死锁 按固定顺序加锁,不嵌套加锁。
使用 RAII 风格 优先使用 lock_guardunique_lock,避免手动调用 lock()/unlock()
避免递归加锁 默认 std::mutex 不支持递归加锁,多次调用 lock() 会导致死锁。

7. 总结

  • 互斥锁(std::mutex 是 C++ 多线程编程中保护共享资源的核心工具。
  • std::lock_guard 提供自动加锁/解锁,适合大多数同步场景。
  • std::unique_lock 提供更灵活的锁管理,适用于复杂同步需求(如条件变量)。
  • 关键点:确保所有共享资源访问都通过互斥锁保护,避免数据竞争和死锁。

通过合理使用互斥锁,可以编写出高效、安全的多线程程序。


网站公告

今日签到

点亮在社区的每一天
去签到