C++ 多线程同步之原子操作(atomic)实战

C++ 多线程同步之原子操作(atomic)实战

C++ 多线程同步之原子操作(atomic)实战

在这里插入图片描述

💡 学习目标:掌握 C++ 标准库中原子操作的使用方法,理解原子操作与互斥锁的区别,能够在轻量级同步场景中高效解决数据竞争问题。
💡 学习重点std::atomic 模板的常用接口、原子操作的特性、原子类型与普通类型的性能对比、原子操作的典型应用场景。

50.1 原子操作的引入背景

在 48 章我们学习了互斥锁,它通过阻塞线程的方式实现临界区保护。
但互斥锁存在上下文切换开销,在一些简单的同步场景中显得过于笨重。
比如对单个变量的自增、自减、赋值等操作,我们需要一种更轻量级的同步方案——原子操作。

⚠️ 注意事项:原子操作仅适用于单个变量的简单同步,无法替代互斥锁实现复杂临界区的保护。

举个例子,使用互斥锁保护变量自增:

#include<iostream>#include<thread>#include<mutex>usingnamespace std;int count =0; mutex mtx;voidincrement(){for(int i =0; i <100000;++i){ lock_guard<mutex>lock(mtx); count++;}}intmain(){ thread t1(increment); thread t2(increment); t1.join(); t2.join(); cout <<"最终 count 值:"<< count << endl;return0;}

这段代码虽然能保证线程安全,但每次加锁解锁都会带来额外开销。
原子操作可以在无锁的情况下实现同样的效果,且效率更高。

50.2 C++ 标准库中的原子操作

C++11 标准库在 <atomic> 头文件中提供了 std::atomic 模板类。
它可以将普通类型包装成原子类型,支持原子化的读、写、修改操作。

50.2.1 std::atomic 的核心特性

  1. 无锁同步:原子操作通过 CPU 指令级别的支持实现同步,不需要操作系统介入上下文切换。
  2. 不可分割性:原子操作的执行过程不会被其他线程打断,要么完全执行,要么完全不执行。
  3. 模板化设计std::atomic 是模板类,可以包装大多数基本数据类型,如 intboollong 等。

50.2.2 常用原子操作接口

std::atomic<int> 为例,常用接口如下:

  • 赋值与读取:支持直接用 = 赋值,用 load() 读取值,默认是顺序一致的内存序。
  • 自增自减fetch_add()(原子自增)、fetch_sub()(原子自减),返回操作前的值。
  • 复合操作exchange()(原子交换值)、compare_exchange_weak()(比较并交换)。
  • 简化操作:支持 ++--+=-= 等重载运算符,使用更便捷。

核心结论:原子操作的重载运算符(如 ++)是对 fetch_add() 的封装,使用起来和普通变量几乎一致。

50.3 原子操作实战:替代互斥锁解决简单同步问题

我们使用 std::atomic<int> 改造 50.1 节的例子,实现无锁同步:

#include<iostream>#include<thread>#include<atomic>usingnamespace std; atomic<int>count(0);// 定义原子类型变量voidincrement(){for(int i =0; i <100000;++i){ count++;// 原子自增操作,无需加锁}}intmain(){ thread t1(increment); thread t2(increment); t1.join(); t2.join(); cout <<"最终 count 值:"<< count << endl;return0;}

运行该程序,最终 count 的值稳定等于 200000
和互斥锁版本相比,这段代码不仅更简洁,而且执行效率更高。

50.3.1 原子操作的内存序(可选进阶知识点)

std::atomic 的操作可以指定内存序,用来平衡性能和同步强度。
常用的内存序有三种:

  1. memory_order_seq_cst:顺序一致内存序,最强的同步保证,也是默认值。
  2. memory_order_acquire/release:适用于生产者-消费者模型,保证操作的先后顺序。
  3. memory_order_relaxed:松散内存序,仅保证操作的原子性,不保证顺序,性能最优。

示例:使用松散内存序优化自增操作

voidincrement(){for(int i =0; i <100000;++i){ count.fetch_add(1, memory_order_relaxed);}}

在只需要保证原子性的场景下,松散内存序可以显著提升性能。

50.4 原子操作 vs 互斥锁:适用场景对比

特性原子操作互斥锁
同步粒度仅支持单个变量的原子操作支持任意复杂的临界区代码
性能开销低(CPU 指令级,无上下文切换)高(可能触发上下文切换)
使用复杂度低(类似普通变量,无需手动加解锁)高(需手动管理锁的生命周期)
死锁风险有(不当使用会导致死锁)

💡 选型技巧

  • 简单变量同步(如计数器、标志位)→ 优先使用原子操作
  • 复杂临界区(如多变量操作、函数调用)→ 必须使用互斥锁

50.5 实战案例 1:原子标志位实现线程退出控制

原子布尔类型 std::atomic<bool> 常用于实现线程的安全退出控制:

#include<iostream>#include<thread>#include<atomic>#include<chrono>usingnamespace std; atomic<bool>is_running(true);// 原子标志位voidworker(){while(is_running.load()){// 原子读取标志位 cout <<"线程正在运行..."<< endl; this_thread::sleep_for(chrono::milliseconds(500));} cout <<"线程安全退出"<< endl;}intmain(){ thread t(worker); this_thread::sleep_for(chrono::seconds(2)); is_running =false;// 原子赋值,通知线程退出 t.join(); cout <<"主线程结束"<< endl;return0;}

运行效果:线程运行 2 秒后,检测到标志位变为 false,安全退出。
这个场景用原子操作比互斥锁更轻量、更高效。

50.6 实战案例 2:原子计数器实现多线程任务统计

使用原子操作实现多线程任务完成情况的统计,无需加锁:

#include<iostream>#include<thread>#include<atomic>#include<vector>usingnamespace std;constint TASK_COUNT =10; atomic<int>completed_tasks(0);// 已完成任务数voidtask(int id){ cout <<"任务"<< id <<"开始执行"<< endl; this_thread::sleep_for(chrono::milliseconds(300)); completed_tasks++;// 原子自增,统计完成数 cout <<"任务"<< id <<"执行完毕"<< endl;}intmain(){ vector<thread> threads;for(int i =1; i <= TASK_COUNT;++i){ threads.emplace_back(task, i);}for(auto& t : threads){ t.join();} cout <<"所有任务执行完毕,总计完成:"<< completed_tasks <<"个"<< endl;return0;}

运行该程序,最终输出的完成任务数一定等于 10
这个案例充分体现了原子操作在简单统计场景下的优势。

50.7 原子操作的常见误区

  1. 忽略内存序的影响
    在多线程通信场景下,随意使用松散内存序可能导致程序行为异常。
    新手建议优先使用默认的顺序一致内存序,熟悉后再优化。
  2. 认为原子操作一定比互斥锁快
    在高竞争场景下,原子操作的自旋等待可能导致 CPU 占用过高。
    此时互斥锁的阻塞机制反而更高效。

误用原子操作保护复杂逻辑
原子操作只能保证单个操作的原子性,不能保护多个原子操作的组合。
例如:

// 错误示例:这两个原子操作的组合不是原子的if(count <100){ count++;}

这个逻辑可能被多个线程打断,需要互斥锁来保护。

50.8 本章小结

  1. 原子操作是轻量级同步方案,通过 CPU 指令级支持实现无锁同步,适用于单个变量的同步场景。
  2. std::atomic 是 C++ 标准库的原子类型模板,支持自增、自减、交换等常用原子操作。
  3. 原子操作和互斥锁各有适用场景:简单变量用原子操作,复杂临界区用互斥锁。
  4. 原子操作的内存序可以平衡性能和同步强度,新手建议先使用默认的顺序一致内存序。

Read more

实测|龙虾机器人(OpenClaw)Windows系统部署全攻略(含避坑指南)

作为一名热衷于折腾新技术的ZEEKLOG博主,最近被一款名为「龙虾机器人」的开源AI工具圈粉了!它还有个更正式的名字——OpenClaw(曾用名Clawdbot、MoltBot),不同于普通的对话式AI,这款工具能真正落地执行任务,比如操作系统命令、管理文件、对接聊天软件、自动化办公,而且支持本地部署,数据隐私性拉满。 不过调研发现,很多小伙伴反馈龙虾机器人在Windows系统上部署容易踩坑,官方文档对Windows的适配细节描述不够细致。今天就结合自己的实测经历,从环境准备、分步部署、初始化配置,到常见问题排查,写一篇保姆级攻略,不管是新手还是有一定技术基础的同学,都能跟着一步步完成部署,少走弯路~ 先简单科普下:龙虾机器人本质是一款开源AI代理框架,核心优势是“能行动、可本地、高灵活”——它不内置大模型,需要对接第三方AI接口(如GPT、Claude、阿里云百炼等),但能将AI的指令转化为实际的系统操作,相当于给AI配了一个“能动手的身体”,这也是它和普通对话大模型的核心区别。另外要注意,它还有一种“生物混合龙虾机器人”的概念,是利用龙虾壳改造的柔性机器人,本文重点分享的是可本

By Ne0inhk
在ESP32-S3部署mimiclaw,基于deepseek并用飞书机器人开展对话-feishu

在ESP32-S3部署mimiclaw,基于deepseek并用飞书机器人开展对话-feishu

最近mimiclaw火爆,其开发团队也在密集更新,我看3天前已经可以用“飞书机器人”对话交互了。 目前网络上能查到的部署资料相对滞后,现在将飞书机器人的部署整理如下: 1. 前提 已经安装好ESP-IDF,并支持vscode编译esp32固件。 2. api-key准备 * 注册deepseek, * 创建APIkey, * 并充值,新注册的用户余额为零,无法使用 3. 飞书机器人 我是在飞书个人版中,创建的机器人。 1. 访问飞书开放平台,单击创建企业自建应用,填写应用名称和描述,选择应用图标,单击创建。 2. 左侧导航栏单击凭证与基础信息 页面,复制App ID(格式如 cli_xxx)和App Secret。 3. 配置事件订阅。 1. 在飞书开放平台左侧导航栏单击事件与回调,在事件配置页签中单击订阅方式,选择使用 长连接 接收事件,单击保存。 2. 在事件配置页面,单击添加事件,

By Ne0inhk
AirSim无人机仿真入门(一):实现无人机的起飞与降落

AirSim无人机仿真入门(一):实现无人机的起飞与降落

概述: 安装好所需要的软件和环境,通过python代码控制无人机进行起飞和降落。 参考资料: 1、知乎宁子安大佬的AirSim教程(文字教程,方便复制) 2、B站瑜瑾玉大佬的30天RL无人机仿真教程(视频教程,方便理解) 3、AirSim官方手册(资料很全,不过是纯英文的) AirSim无人机仿真入门(一):实现无人机的起飞与降落 * 1 安装AirSim * 1.1 参考教程 * 1.2 内容梳理 * 1.3 步骤总结 * 2 开始使用 AirSim * 2.1 参考教程 * 2.2 内容梳理 * 2.3 步骤总结 * 3 撰写python控制程序 * 3.1 参考教程 * 3.2 内容梳理

By Ne0inhk
HarmonyOS6 底部导航栏组件 rc_concave_tabbar 使用指南

HarmonyOS6 底部导航栏组件 rc_concave_tabbar 使用指南

文章目录 * 前言 * 组件特性 * 适用场景 * 使用说明 * 安装组件 * 安装步骤 * 步骤一:引入相关依赖 * 步骤二:创建菜单数据 * 步骤三:使用导航组件 * 运行效果 * 参数介绍 * TabsConcaveCircle 组件参数 * TabMenusInterfaceIRequired 菜单项配置 * 进阶使用 * 自定义单个菜单项颜色 * 调整动画速度 * 自定义高度和颜色 * 注意事项 * 总结 前言 rc_concave_tabbar 是一个功能强大、样式精美的 HarmonyOS 底部导航栏组件库,提供凹陷圆形动画效果样式,适用于多种场景。本篇将介绍 rc_concave_tabbar 的使用方法以及其相关的设计理念。 组件特性 * 流畅动画:支持流畅的凹陷圆形切换动画效果 * 高度定制:支持自定义背景色、字体颜色、高度等多种样式配置 * 灵活配置:支持全局配置和单项配置,满足不同场景需求

By Ne0inhk