跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C/C++ 并发编程:多线程与多进程锁的种类、特点及场景

系统梳理了 C/C++ 并发编程中多线程与多进程常用的锁机制。内容包括互斥锁、自旋锁、读写锁、条件变量及递归锁等线程同步工具,以及文件锁、共享内存锁和信号量等进程间同步方案。文章对比了各类锁的核心特点、优缺点及适用场景,提供了 C++11/17 标准库及 Linux 系统调用的实战示例,并给出了基于临界区长度、读写比例及资源类型的选型决策树与避坑指南,旨在帮助开发者平衡并发性能与数据安全性。

草莓泡芙发布于 2026/3/27更新于 2026/10/18107 浏览

在并发编程中,'锁'是解决资源竞争、保证数据一致性的核心工具 —— 无论是多线程共享进程内资源,还是多进程跨地址空间协作,都需要通过锁来规范临界区的访问逻辑。本文结合 C/C++ 开发场景(含 Linux 系统调用、C++11 + 标准库),系统梳理多线程 / 多进程中常用锁的种类、核心特点,并给出实战选型建议。

一、核心概念铺垫

在讲锁之前,先明确两个关键前提,避免混淆:

  • 临界区:并发场景中多个执行流(线程 / 进程)共同访问的共享资源(如全局变量、文件、网络连接),是锁的保护对象。
  • 线程 vs 进程:
    • 多线程:共享同一进程的地址空间,锁可直接基于进程内内存实现(开销小);
    • 多进程:地址空间独立,锁需借助'跨进程共享资源'(如文件、共享内存)实现(开销略大)。

二、多线程锁(线程间同步)

多线程锁用于保护进程内的共享资源(如全局变量、堆内存),核心是'线程间互斥 / 同步',常见类型如下:

1. 互斥锁(Mutex):并发编程的'基础款'
核心特点
  • 本质:二进制锁(要么锁定,要么解锁),保证同一时刻只有一个线程进入临界区。
  • 等待机制:线程获取不到锁时,会从'运行态'转为'阻塞态',放弃 CPU 资源(不占用 CPU 空转)。
  • 适用场景:临界区执行时间较长(如 IO 操作、复杂计算),避免 CPU 浪费。
  • 优缺点:
    • 优点:开销低、使用简单,无 CPU 空转;
    • 缺点:上下文切换开销(阻塞 / 唤醒线程时),适合临界区耗时 > 上下文切换耗时的场景。
C++ 实战示例(C++11 标准库)
#include <mutex>
#include <thread>

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

void increment() {
    std::lock_guard<std::mutex> lock(mtx); // RAII 风格,自动加锁/解锁,避免漏解锁
    shared_data++; // 临界区:保护共享变量
}

int main() {
    std::thread t1(increment), t2(increment);
    t1.join();
    t2.join();
    return 0;
}
  • 关键 API:std::mutex(基础互斥锁)、std::lock_guard(RAII 自动管理)、std::unique_lock(更灵活,支持手动解锁 / 延迟加锁)。
2. 自旋锁(Spinlock):临界区极短场景的'性能款'
核心特点
  • 本质:忙等锁,线程获取不到锁时,会循环检测锁状态(不放弃 CPU),直到获取成功。
  • 适用场景:临界区执行时间极短(如简单的变量读写),上下文切换开销 > 临界区耗时。
  • 优缺点:
    • 优点:无上下文切换开销,响应速度快;
    • 缺点:CPU 空转(忙等),高并发下会占用大量 CPU 资源,可能导致'优先级反转'。
实战注意(Linux 场景)
  • C++ 标准库无自旋锁实现,需借助系统调用(如 pthread_spinlock_t)或第三方库(如 Boost);
  • 禁用场景:单核 CPU(忙等会阻塞所有线程)、临界区耗时较长(CPU 空转浪费严重)。
3. 读写锁(RWLock):读多写少场景的'优化款'
核心特点
  • 本质:区分读 / 写操作,遵循'读共享、写独占'原则:
    • 多个线程可同时获取读锁(读操作不冲突);
    • 写锁会阻塞所有读锁和其他写锁(写操作独占资源)。
  • 适用场景:读操作远多于写操作(如缓存查询、配置读取),大幅提升并发效率。
  • 优缺点:
    • 优点:读操作并发度高,比互斥锁更高效;
    • 缺点:实现复杂,写操作可能出现'饥饿'(读锁一直占用,写锁长期等待),需设置公平策略。
C++ 实战示例(C++17+)
#include <shared_mutex>
#include <thread>
#include <vector>

std::shared_mutex rw_mtx;
int cache_data = 0; // 读操作:获取共享锁(读锁)

void read_cache() {
    std::shared_lock<std::shared_mutex> lock(rw_mtx); // 读取 cache_data,无写操作
}

// 写操作:获取独占锁(写锁)
void update_cache() {
    std::unique_lock<std::shared_mutex> lock(rw_mtx);
    cache_data++; // 写操作,独占资源
}
  • 注意:C++11/14 无 std::shared_mutex,需使用 boost::shared_mutex。
4. 条件变量(Condition Variable):线程同步的'通信款'
核心特点
  • 本质:配合互斥锁使用,实现线程间'等待 - 通知'机制,解决'忙等同步'问题(如生产者 - 消费者模型)。
  • 核心逻辑:线程 A 获取互斥锁后,若条件不满足,会释放锁并阻塞等待;线程 B 满足条件后,通知线程 A 唤醒并重新获取锁。
  • 适用场景:线程间需要基于某个条件协作(如生产者生产数据后通知消费者消费)。
  • 优缺点:
    • 优点:避免忙等,减少 CPU 占用,同步逻辑清晰;
    • 缺点:必须与互斥锁搭配使用,易出现'虚假唤醒'(需用 while 循环判断条件)。
C++ 实战示例(生产者 - 消费者)
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;

// 生产者
void producer() {
    for (int i = 0; i < 10; i++) {
        std::unique_lock<std::mutex> lock(mtx);
        q.push(i);
        cv.notify_one(); // 通知一个等待的消费者
    }
}

// 消费者
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 避免虚假唤醒,用 while 循环判断条件
        cv.wait(lock, []() { return !q.empty(); });
        int data = q.front();
        q.pop();
        lock.unlock(); // 提前解锁,提高并发
    }
}
5. 递归锁(Recursive Mutex):递归调用的'安全款'
核心特点
  • 本质:允许同一线程多次获取锁(自动计数,解锁次数需与加锁次数一致),避免递归调用时的死锁。
  • 适用场景:函数递归调用时需要访问临界区,或同一线程在多个嵌套函数中需要加锁。
  • 优缺点:
    • 优点:解决递归场景的死锁问题;
    • 缺点:可能隐藏逻辑漏洞(如误判临界区范围),开销略高于普通互斥锁,不建议滥用。
C++ 实战示例
#include <mutex>

std::recursive_mutex rec_mtx;
int count = 0;

void recursive_func(int depth) {
    if (depth == 0) return;
    std::lock_guard<std::recursive_mutex> lock(rec_mtx);
    count++;
    recursive_func(depth - 1); // 同一线程再次加锁,不会死锁
}

三、多进程锁(进程间同步)

多进程的地址空间相互独立,锁需借助'跨进程共享资源'实现,常见类型如下:

1. 文件锁(File Lock):最易实现的跨进程锁
核心特点
  • 本质:以文件作为共享资源,通过文件的'锁定状态'实现进程间互斥,分为两种:
    • flock:建议性锁(进程可忽略),支持整个文件锁定,接口简单;
    • fcntl:强制性锁(需系统配置),支持'记录锁'(锁定文件部分内容),更灵活。
  • 适用场景:进程间共享文件(如日志写入、配置文件修改),实现简单,无需额外依赖。
  • 优缺点:
    • 优点:实现简单,无需手动管理共享资源,进程崩溃后锁自动释放;
    • 缺点:依赖文件系统,性能略低,不适合高频通信场景。
Linux 系统调用示例(fcntl 记录锁)
#include <fcntl.h>
#include <unistd.h>

// 加锁:fd 为文件描述符,offset 和 len 为锁定范围
int lock_file(int fd) {
    struct flock fl;
    fl.l_type = F_WRLCK; // 写锁(独占),读锁为 F_RDLCK
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0; // 0 表示锁定整个文件
    return fcntl(fd, F_SETLK, &fl); // 非阻塞,失败返回 -1
}

// 解锁
int unlock_file(int fd) {
    struct flock fl;
    fl.l_type = F_UNLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;
    return fcntl(fd, F_SETLK, &fl);
}
2. 共享内存锁:高性能跨进程锁
核心特点
  • 本质:通过共享内存存储锁状态(如自旋锁、互斥锁),进程间直接访问共享内存获取锁,无文件 IO 开销。
  • 适用场景:高频通信的多进程场景(如进程池、分布式计算),追求极致性能。
  • 优缺点:
    • 优点:性能极高(内存级访问);
    • 缺点:需手动管理共享内存的创建 / 销毁,进程崩溃后可能导致锁残留(需处理异常)。
实现思路(Linux)
  1. 用 shmget 创建共享内存,shmat 将其映射到进程地址空间;
  2. 在共享内存中定义锁结构(如自旋锁);
  3. 进程通过访问共享内存中的锁状态实现互斥。
3. 信号量(Semaphore):通用跨进程 / 线程同步工具
核心特点
  • 本质:计数器锁,支持'计数'(非二进制),核心逻辑:
    • 申请资源(P 操作):计数器 - 1,若计数器 < 0 则阻塞;
    • 释放资源(V 操作):计数器 + 1,若计数器 <= 0 则唤醒一个阻塞进程 / 线程。
  • 适用场景:
    • 线程 / 进程间互斥(计数器 = 1,等价于互斥锁);
    • 限制资源访问数量(如线程池大小、连接池数量)。
  • 分类:
    • System V 信号量:适合多进程同步,需手动管理(semget 创建、semctl 销毁);
    • Posix 信号量:支持线程 / 进程同步,接口更简洁(sem_init 创建、sem_destroy 销毁)。
C++ 实战示例(Posix 信号量,多进程)
#include <semaphore.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    // 创建命名信号量(跨进程可见),初始值 1(互斥锁)
    sem_t* sem = sem_open("/my_sem", O_CREAT | O_EXCL, 0644, 1);
    if (fork() == 0) {
        sem_wait(sem); // 子进程 P 操作(加锁)
        // 临界区:访问共享资源
        sem_post(sem); // 子进程 V 操作(解锁)
        sem_close(sem);
        return 0;
    }
    sem_wait(sem); // 父进程 P 操作(加锁)
    // 临界区:访问共享资源
    sem_post(sem); // 父进程 V 操作(解锁)
    wait(nullptr);
    sem_close(sem);
    sem_unlink("/my_sem"); // 删除信号量
    return 0;
}

四、锁的核心对比与选型决策树

1. 关键维度对比表
锁类型适用场景核心优点核心缺点实现方式
互斥锁多线程,临界区较长无 CPU 空转,使用简单上下文切换开销C++11 std::mutex
自旋锁多线程,临界区极短无上下文切换,响应快CPU 空转,易优先级反转Linux pthread_spinlock_t
读写锁多线程,读多写少读操作并发度高写操作可能饥饿C++17 std::shared_mutex
条件变量多线程,等待 - 通知同步避免忙等,同步逻辑清晰需配合互斥锁,有虚假唤醒C++11 std::condition_variable
递归锁多线程,递归调用避免递归死锁可能隐藏逻辑漏洞C++11 std::recursive_mutex
文件锁多进程,共享文件实现简单,锁自动释放性能低,依赖文件系统Linux flock/fcntl
共享内存锁多进程,高频通信性能极高需手动管理共享内存Linux shmget + 自定义锁
信号量线程 / 进程,资源计数通用灵活,支持计数需手动管理,易出错Posix/System V 信号量
2. 选型决策树(实战优先)
  1. 先判断:是多线程还是多进程?
    • 多线程 → 优先选'线程锁'(开销低);
    • 多进程 → 优先选'进程锁'(依赖共享资源)。
  2. 多线程场景再细化:
    • 读多写少 → 读写锁;
    • 临界区 < 1ms → 自旋锁;
    • 临界区 > 1ms → 互斥锁;
    • 递归调用 → 递归锁;
    • 线程间需等待通知 → 条件变量。
  3. 多进程场景再细化:
    • 共享文件 → 文件锁;
    • 高频通信 → 共享内存锁;
    • 资源计数 / 通用同步 → 信号量。

五、实战避坑指南(面试高频)

  1. 死锁预防:
    • 加锁顺序一致(避免 A 拿锁 1 等锁 2,B 拿锁 2 等锁 1);
    • 避免锁嵌套(如需嵌套,控制层数并明确顺序);
    • 使用超时锁(如 std::unique_lock::try_lock_for),避免无限等待。
  2. 性能优化:
    • 减少临界区范围(只锁必要代码,不锁整个函数);
    • 读多写少用读写锁,避免用互斥锁浪费并发;
    • 多进程高频通信避免文件锁,选共享内存锁。
  3. 常见误区:
    • 条件变量用 if 判断条件(易触发虚假唤醒,需用 while);
    • 自旋锁用在单核 CPU(忙等会阻塞所有线程);
    • 递归锁滥用(可能掩盖临界区设计问题)。

总结

锁的核心是'平衡并发与安全'—— 没有最好的锁,只有最适合的场景。实际开发中,需先明确'线程 / 进程''临界区特性''资源类型'三个关键维度,再结合锁的优缺点选型。本文梳理的锁类型、特点及选型逻辑,既适用于日常开发沉淀,也能应对 C/C++ 并发编程的面试提问。

目录

  1. 一、核心概念铺垫
  2. 二、多线程锁(线程间同步)
  3. 1. 互斥锁(Mutex):并发编程的“基础款”
  4. 核心特点
  5. C++ 实战示例(C++11 标准库)
  6. 2. 自旋锁(Spinlock):临界区极短场景的“性能款”
  7. 核心特点
  8. 实战注意(Linux 场景)
  9. 3. 读写锁(RWLock):读多写少场景的“优化款”
  10. 核心特点
  11. C++ 实战示例(C++17+)
  12. 4. 条件变量(Condition Variable):线程同步的“通信款”
  13. 核心特点
  14. C++ 实战示例(生产者 - 消费者)
  15. 5. 递归锁(Recursive Mutex):递归调用的“安全款”
  16. 核心特点
  17. C++ 实战示例
  18. 三、多进程锁(进程间同步)
  19. 1. 文件锁(File Lock):最易实现的跨进程锁
  20. 核心特点
  21. Linux 系统调用示例(fcntl 记录锁)
  22. 2. 共享内存锁:高性能跨进程锁
  23. 核心特点
  24. 实现思路(Linux)
  25. 3. 信号量(Semaphore):通用跨进程 / 线程同步工具
  26. 核心特点
  27. C++ 实战示例(Posix 信号量,多进程)
  28. 四、锁的核心对比与选型决策树
  29. 1. 关键维度对比表
  30. 2. 选型决策树(实战优先)
  31. 五、实战避坑指南(面试高频)
  32. 总结

更多推荐文章

查看全部
  • 滑动窗口算法实战:串联所有单词的子串与最小覆盖子串
  • 星辰 RPA 与 Agent:构建小红书自动发文机器人
  • 深度强化学习基础架构与核心算法实现
  • Spring Boot 数据可视化与图表集成实战
  • 基于 DeepSeek-OCR-WEBUI 镜像的本地 OCR 部署实战
  • Python 学习路线与核心技能树详解
  • AIGC 核心技术解析:GPT、BERT 与 Transformer 工作原理
  • Linux 系统安装 Go 语言及环境配置指南
  • 基于 Vue3+Nuxt3 的在线教育系统前端部署指南
  • 《Agent Runtime 工程化》第十章 Eval Harness 与 CI 回归:导读
  • 延迟渲染中的 C++ 实现要点与性能权衡
  • C++ 多态原理详解:虚函数表与动态绑定
  • Python 潮流周刊 #1:系统自学 Python 指南
  • 动手学 PyTorch:从线性回归到图像分类
  • 鸿蒙 HarmonyOS 开发技术入门与实战指南
  • 2026 年 3 月 AI 领域动态:近 7 天全球热点事件梳理
  • Windows 部署 OpenClaw 接入飞书机器人
  • Python 核心应用领域解析:Web 开发、数据科学与脚本自动化
  • 希尔排序算法详解:原理、实现与优化
  • FPGA 是什么:从原理到应用场景的通俗解读

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online