【C++】一篇文章悲观锁与乐观锁与其思想在C++语言中的应用

悲观锁(Pessimistic Lock)

很悲观,认为每次拿数据都会被别人修改,所以拿的时候就上锁。

  • 假设并发冲突一定会发生
  • 操作前先加锁,其他人必须等待
  • 阻塞式、排他锁

典型实现

  • 数据库:select ... for update
  • Java:synchronizedReentrantLock

优点

  • 强一致性,冲突多的时候更稳定
  • 写多读少场景效率高

缺点

  • 锁竞争、上下文切换开销大
  • 冲突少的时候浪费性能

乐观锁(Optimistic Lock)

核心思想:很乐观,认为一般不会冲突,只在更新时检查是否被改。

  • 假设冲突很少发生
  • 读不加锁,更新时才校验
  • 是非阻塞、无锁设计

典型实现

  1. 版本号机制(最常用)

    • 加字段 version

    • 更新时:

      sql 复制代码
      update table
      set ..., version = version + 1
      where id = ? and version = ?
    • 影响行数=0 说明冲突,重试/报错

  2. CAS(Compare And Swap)思想

    • 比较旧值 → 相等才更新
    • C++ : std::atomic原子类
    • Java:AtomicInteger 原子类

优点

  • 无锁,高并发、读多写少极快
  • 无死锁风险

缺点

  • 冲突多会导致大量重试,CPU 飙升
  • 只能保证单个变量原子性(ABA 问题)

简洁总结

  • 悲观锁:先上锁,再操作 → 安全但慢
  • 乐观锁:不加锁,更新时校验 → 快但要处理冲突

使用场景

  • 读多写少、高并发 → 乐观锁
  • 写多读少、要求强一致 → 悲观锁

在C++后端开发中,乐观锁和悲观锁的使用场景非常明确,核心是根据并发冲突概率业务一致性要求来选择。下面结合C++的具体实现和后端开发的典型场景,给你讲清楚怎么用、用在哪。

悲观锁在C++中的应用场景

悲观锁在C++中主要依赖互斥锁、自旋锁 等同步原语实现,核心用于写操作频繁、冲突概率高、强一致性要求的场景。

1. 实现(C++标准库std::mutex)

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

// 全局互斥锁(悲观锁核心)
std::mutex mtx;
int shared_data = 0;

// 写操作函数(多线程调用)
void write_data(int value) {
    // 加锁(悲观:认为一定会有竞争,先锁再操作)
    std::lock_guard<std::mutex> lock(mtx);
    shared_data = value;
    std::cout << "写入数据:" << shared_data << std::endl;
}

int main() {
    std::thread t1(write_data, 10);
    std::thread t2(write_data, 20);
    t1.join();
    t2.join();
    return 0;
}

2. 使用场景

(1)多线程操作共享资源(写多读少)
  • 场景:网关服务中多线程修改连接池、配置中心多线程更新配置、订单系统多线程修改订单状态。
  • 原因:写操作频繁,冲突概率高,悲观锁能保证绝对的线程安全,避免数据错乱。
  • 扩展:除了std::mutex,还会用std::recursive_mutex(可重入锁)、pthread_mutex_t(POSIX锁)。
(2)数据库层面的排他锁
  • 场景:金融系统扣减余额、秒杀系统扣减库存(C++程序调用SQL)。

  • 实现:C++程序执行select ... for update语句,锁定数据库行,避免并发更新导致数据不一致。

    cpp 复制代码
    // 伪代码:C++调用MySQL执行悲观锁SQL
    std::string sql = "SELECT balance FROM user WHERE id = 1 FOR UPDATE";
    mysql_query(conn, sql.c_str());
    // 扣减余额(此时该行已被锁定,其他线程/进程无法修改)
    sql = "UPDATE user SET balance = balance - 100 WHERE id = 1";
    mysql_query(conn, sql.c_str());
(3)临界区操作(必须原子执行)
  • 场景:日志系统多线程写入同一个日志文件、网络服务多线程修改全局连接计数。
  • 原因:这类操作不能被打断,悲观锁能保证临界区代码完整执行。

乐观锁在C++中的应用场景

乐观锁在C++中主要依赖CAS(Compare-And-Swap) 实现(C++11起提供原子操作库),核心用于读多写少、冲突概率低、追求高性能的场景。

典型实现(C++原子类/CAS)

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

// 原子变量(底层基于CAS,乐观锁核心)
std::atomic<int> shared_data = 0;

// 更新数据函数(多线程调用)
void update_data(int value) {
    int expected = shared_data.load(); // 读取当前值
    // CAS操作(乐观:先读值,更新时检查是否被修改,没修改才更新)
    while (!shared_data.compare_exchange_weak(expected, value)) {
        // 冲突时重试(expected会自动更新为最新值)
        std::cout << "数据已被修改,重试更新..." << std::endl;
    }
    std::cout << "成功更新数据:" << shared_data << std::endl;
}

int main() {
    std::thread t1(update_data, 10);
    std::thread t2(update_data, 20);
    t1.join();
    t2.join();
    return 0;
}

2. 使用场景

(1)高并发计数器(读多写少)
  • 场景:接口访问量统计、缓存命中率统计、秒杀系统的参与人数计数。
  • 原因:读操作远多于写操作,冲突概率低,CAS比互斥锁性能高一个量级(无锁开销)。
  • 扩展:C++11的std::atomic系列(atomic_intatomic_long)都是乐观锁实现,后端中常用于高性能统计。
(2)数据库版本号控制(C++程序配合DB)
  • 场景:电商系统更新商品库存(读多写少,大部分是查询库存,少量扣减)、用户资料修改(大部分是查看,少量编辑)。

  • 实现:C++程序通过版本号实现乐观锁,避免数据库锁竞争:

    cpp 复制代码
    // 伪代码:C++调用MySQL实现乐观锁
    // 1. 查询商品信息(含版本号)
    std::string sql = "SELECT stock, version FROM goods WHERE id = 1";
    mysql_query(conn, sql.c_str());
    int stock = ...; // 读取库存
    int version = ...; // 读取版本号
    
    // 2. 扣减库存(更新时校验版本号)
    sql = "UPDATE goods SET stock = stock - 1, version = version + 1 "
          "WHERE id = 1 AND version = " + std::to_string(version);
    int affected_rows = mysql_affected_rows(conn);
    if (affected_rows == 0) {
        // 版本号不匹配,说明有并发更新,重试或返回错误
        std::cout << "库存更新冲突,请重试" << std::endl;
    }
(3)无锁数据结构(高性能组件)
  • 场景:网关服务的无锁队列、缓存系统的无锁哈希表、高并发RPC框架的无锁连接池。
  • 原因:后端中间件对性能要求极高,无锁数据结构(基于乐观锁CAS)能避免锁竞争,提升吞吐量。
  • 示例:C++实现无锁栈(核心是CAS操作节点指针)。

乐观锁与悲观锁的选择准则

维度 悲观锁 乐观锁
适用场景 写多读少、冲突率高、强一致性 读多写少、冲突率低、高性能要求
C++实现 std::mutex、pthread_mutex std::atomic(CAS)、版本号
性能开销 锁竞争、上下文切换开销大 无锁开销,冲突时重试开销
典型应用 订单修改、余额扣减、日志写入 计数器、库存查询更新、无锁队列
相关推荐
pilifeng19 分钟前
Ubuntu Linux 目录结构详解
linux·ubuntu
电棍23315 分钟前
ubuntu网卡失效
linux
律宏阔19 分钟前
VMware 能识别 USB 设备,但无法连接(灰色)
linux
YangYang9YangYan24 分钟前
2026 电商数据运营校招 JD 梳理|岗位任务、技能与面试考点汇总
大数据·数据库·数据分析
Sun 328525 分钟前
读懂集中式主备的集群状态与节点角色
数据库·opengauss·集群·节点角色·磐维数据库·运行状态
Rabitebla26 分钟前
【Linux系统编程】指令(三):创建、删除、复制、移动、看内容
linux·数据结构·c++·算法
程序员老陆32 分钟前
CMake 常用关键字与命令全景:从 set到 target_link_options的现代工程视角
c++·cmake·程序开发
事已至此先睡覺吧36 分钟前
第三篇:Java 流程控制详解:条件判断、循环与跳转语句
java·开发语言
AR-26710-43 分钟前
Linux Day9——复习第一部分(实操产出)
linux
m0_734571761 小时前
深入理解5g <二十> dmrs
开发语言·5g