C++ 互斥锁、读写锁、原子操作、条件变量

前言:在多线程的实现中,对临界资源的访问容易产生冲突与竞争。C++提供了一些方法来解决这种资源冲突,如,互斥锁、读写锁、原子操作、条件变量。本文将对这四种方式进行一一介绍。

目录

一、互斥锁(std::mutex)

[1.1 原理](#1.1 原理)

[1.2 C++库](#1.2 C++库)

[1.3 代码示例](#1.3 代码示例)

[1.4 注意事项](#1.4 注意事项)

二、读写锁(std::shared_mutex)

[2.1 原理](#2.1 原理)

[2.2 C++库](#2.2 C++库)

[2.3 代码示例](#2.3 代码示例)

三、原子操作(std::atomic)

[3.1 原理](#3.1 原理)

[3.2 C++库](#3.2 C++库)

[3.3 代码示例](#3.3 代码示例)

[3.4 注意事项](#3.4 注意事项)

四、条件变量(std::condition_variable)

[4.1 原理](#4.1 原理)

[4.2 C++库](#4.2 C++库)

[4.3 代码示例](#4.3 代码示例)

[4.4 注意事项](#4.4 注意事项)


一、互斥锁(std::mutex)

1.1 原理

互斥锁是最基础的排他性同步语言,其核心逻辑是:同一时刻仅允许一个线程持有锁,其他线程尝试加锁时会阻塞或失败。

1.2 C++库

类型 特点
std::mutex 基础互斥锁,非递归、无超时,加锁失败则阻塞
std::recursive_mutex 递归互斥锁,允许同一线程多次加锁(需对应次数解锁)
std::timed_mutex 带超时的互斥锁,支持try_lock_for/try_lock_until(超时返回 false)

1.3 代码示例

cpp 复制代码
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex mtx;
int counter = 0; // 共享资源

// 临界区操作:自增计数器
void increment(int n) {
    for (int i = 0; i < n; ++i) {
        // 推荐RAII:lock_guard(作用域结束自动解锁,避免死锁)
        std::lock_guard<std::mutex> lock(mtx);
        counter++; // 临界区(独占访问)
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(increment, 100000);
    }
    for (auto& t : threads) t.join();
    std::cout << "最终计数器:" << counter << std::endl; // 预期400000
    return 0;
}

1.4 注意事项

避免死锁,遵循"加锁顺序一致、避免嵌套锁"。

二、读写锁(std::shared_mutex)

2.1 原理

读写锁是互斥锁的优化版,核心思想是:读共享,写排他。即,多个线程可同时持有读锁,但写锁阻塞所有读锁,写操作独占。适合"读多写少"的情况(如缓存配置、日志查询)。

2.2 C++库

C++17引入std::shared_mutex

2.3 代码示例

cpp 复制代码
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>

std::shared_mutex rw_mtx;
int cache_data = 0; // 读多写少的共享缓存

// 读线程:共享读锁(多线程同时读)
void read_cache(int id) {
    std::shared_lock<std::shared_mutex> lock(rw_mtx); // 加读锁
    std::cout << "读线程" << id << "读取:" << cache_data << std::endl;
}

// 写线程:排他写锁(独占)
void update_cache(int val) {
    std::lock_guard<std::shared_mutex> lock(rw_mtx); // 加写锁
    cache_data = val;
    std::cout << "写线程更新为:" << val << std::endl;
}

int main() {
    std::vector<std::thread> threads;
    // 5个读线程(同时执行)
    for (int i = 0; i < 5; ++i) threads.emplace_back(read_cache, i);
    // 1个写线程(阻塞所有读线程)
    threads.emplace_back(update_cache, 100);
    for (auto& t : threads)
    {
        t.join();
    }
    return 0;
}

三、原子操作(std::atomic)

3.1 原理

原子操作是无锁同步,其原理是:通过CPU指令级原子性(如lock 前缀)实现,无需内核调度,适用于简单变量的"读 - 改 - 写"(如计数器、标志位等)。

3.2 C++库

C++11引入std::atomic模板,支持基本数据类型,方法调用包括:

操作 作用 原子性
operator++/operator+= 自增 / 加法赋值
fetch_add(n) 加 n 并返回旧值
load()/store(v) 读取 / 写入值(内存序可控)
compare_exchange_weak/strong CAS(比较并交换)

3.3 代码示例

cpp 复制代码
#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

std::atomic<int> atomic_counter(0); // 原子计数器

void atomic_increment(int n) {
    for (int i = 0; i < n; ++i) {
        atomic_counter++; // 原子自增(无锁)
        // 等价于:atomic_counter.fetch_add(1, std::memory_order_relaxed);
    }
}

// 高级:CAS实现无锁更新
void cas_demo() {
    int expected = 0;
    int new_val = 100;
    // 弱CAS(可能虚假失败,需循环)
    while (!atomic_counter.compare_exchange_weak(expected, new_val)) {
        std::cout << "CAS失败,当前值:" << expected << std::endl;
    }
    std::cout << "CAS成功,值:" << atomic_counter << std::endl;
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) threads.emplace_back(atomic_increment, 100000);
    for (auto& t : threads) t.join();
    std::cout << "原子计数器:" << atomic_counter << std::endl; // 400000
    cas_demo();
    return 0;
}

3.4 注意事项

仅适合简单变量操作,复杂逻辑仍需要加锁。

四、条件变量(std::condition_variable)

4.1 原理

条件变量解决线程间时序依赖(生产者生产后消费者才能消费),其核心逻辑是:线程A(等待方)阻塞等待条件满足,释放持有的互斥锁,线程B(通知方)满足条件后唤醒等待线程,重新竞争互斥锁。必须与互斥锁配合使用。

4.2 C++库

C++11提供std::consition_variable (仅配合std::unique_lock )与std::condition_variable_any(配合任意类型锁)。

4.3 代码示例

cpp 复制代码
#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> q; // 共享队列(临界资源)

// 生产者:生产数据,通知消费者
void producer(int n) {
    for (int i = 0; i < n; ++i) {
        std::unique_lock<std::mutex> lock(mtx); // 加锁
        q.push(i); // 生产数据
        std::cout << "生产者生产:" << i << std::endl;
        lock.unlock(); // 可选:提前解锁,减少消费者等待
        cv.notify_one(); // 唤醒一个等待的消费者
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 消费者:等待数据,消费
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 循环等待:防止虚假唤醒
        cv.wait(lock, []() { return !q.empty(); });
        // 条件满足,消费数据
        int val = q.front();
        q.pop();
        std::cout << "消费者消费:" << val << std::endl;
        lock.unlock();
        if (val == 9) break; // 消费完退出
    }
}

int main() {
    std::thread prod(producer, 10);
    std::thread cons(consumer);
    prod.join();
    cons.join();
    return 0;
}

4.4 注意事项

std::condition_variable 仅支持std::unique_lock,需要手动控制锁的释放。

相关推荐
551只玄猫11 分钟前
【数学建模 matlab 实验报告12】聚类分析和判别分析
开发语言·数学建模·matlab·课程设计·聚类·实验报告
念恒123061 小时前
继承(下) (Inheritance)
c++
小陈工2 小时前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
H Journey2 小时前
C++之 CMake、CMakeLists.txt、Makefile
开发语言·c++·makefile·cmake
研究点啥好呢6 小时前
Github热门项目推荐 | 创建你的像素风格!
c++·python·node.js·github·开源软件
_dindong6 小时前
cf1091div2 C.Grid Covering(数论)
c++·算法
lly2024067 小时前
C 标准库 - `<stdio.h>`
开发语言
沫璃染墨7 小时前
C++ string 从入门到精通:构造、迭代器、容量接口全解析
c语言·开发语言·c++
jwn9997 小时前
Laravel6.x核心特性全解析
开发语言·php·laravel
迷藏4947 小时前
**发散创新:基于Rust实现的开源合规权限管理框架设计与实践**在现代软件架构中,**权限控制(RBAC)** 已成为保障
java·开发语言·python·rust·开源