std::atomic

一、概述

std::atomic 是C++11引入的一个模板类,用于提供原子操作的类型。在多线程编程中,当多个线程同时访问同一块数据时,可能会导致数据竞争和不确定的行为。std::atomic 可以用来创建原子类型的变量,保证对该变量的操作是原子的,不会被中断,从而避免了数据竞争。

std::atomic 适用于以下场景:

  1. 在多线程环境下对共享数据进行原子操作;
  2. 需要保证特定操作的原子性,如递增、递减、交换等操作;
  3. 需要避免使用锁的情况下进行线程同步。

举个具体的例子:

#include <iostream>
#include <thread>
#include <mutex>int counter = 0;void incrementCounter()
{for (int i = 0; i < 100000; ++i){counter++;}
}int main(int argc, char *argv[])
{std::thread t1(incrementCounter);std::thread t2(incrementCounter);t1.join();t2.join();qDebug() << counter;return 0;
}

这段代码存在一个线程安全的问题。多个线程同时访问和修改同一个全局变量 counter ,而没有进行同步操作会导致竞态条件

竞态条件指的是多个线程并发执行时,由于执行顺序不确定导致程序出现意料之外的结果的情况。

就上面的代码来说,在 incrementCounter() 函数中,多个线程同时对 counter 进行递增操作,而递增操作不是一个原子操作,它包括读取 counter 的当前值、对该值加一、然后写回到 counter。由于线程间的执行顺序是不确定的,就可能出现以下情况:

  1. 线程 A 读取 counter 的值为 0,然后执行加一操作得到 1;
  2. 此时线程 B 也读取 counter 的值为 0,执行加一操作得到 1;
  3. 然后线程 A 和线程 B 都把值 1 写回到 counter,导致实际的递增次数少于预期。

因此,竞态条件可能会导致 counter 的最终结果少于预期的值。

为了解决这个问题,可以使用互斥锁来保护对 counter 的访问,确保同时只有一个线程能够访问和修改 counter,从而避免了竞态条件的问题:

int counter = 0;
std::mutex mutex;void incrementCounter()
{for (int i = 0; i < 100000; ++i){std::lock_guard<std::mutex> lock(mutex);counter++;}
}int main(int argc, char *argv[])
{std::thread t1(incrementCounter);std::thread t2(incrementCounter);t1.join();t2.join();qDebug() << counter;return 0;
}

使用std::atomic是另一种做法:

#include <atomic>std::atomic<int> counter(0);void incrementCounter()
{for (int i = 0; i < 100000; ++i){counter++;}
}int main(int argc, char *argv[])
{std::thread t1(incrementCounter);std::thread t2(incrementCounter);t1.join();t2.join();qDebug() << counter;return 0;
}

使用std::atomic<int> 类型的计数器 counter,并在两个线程中并发地对其进行递增操作。由于 counter 是 std::atomic 类型,它会保证递增操作的原子性,避免了多线程竞争导致的问题。最终输出的 counter 值会是预期的 200000。

二、成员函数

1、compare_exchange_weak()compare_exchange_strong()

比较和交换函数。这两个函数的作用都是:当原子对象的值等于期望值时,用新值替换原子对象的值。

compare_exchange_weak 函数

  • 比较和交换不成功时,允许原子变量的值被其他线程更改,并返回 false,表示失败。
  • 比较和交换成功时,将原子变量的值设置为新值,并返回 true,表示成功。
std::atomic<int> atomic_var(10);void increment(std::atomic<int>& var)
{int expected = 10;int new_value = 20;bool success = var.compare_exchange_weak(expected, new_value);if (success) {std::cout << "线程 " << std::this_thread::get_id()<< std::endl<< " 比较和交换成功,原子变量的值已修改为: " << var << std::endl;} else {std::cout << "线程 " << std::this_thread::get_id()<< std::endl<< " 比较和交换失败,原子变量的值未修改" << std::endl;}
}int main(int argc, char *argv[])
{std::thread t1(increment, std::ref(atomic_var));std::thread t2(increment, std::ref(atomic_var));t1.join();t2.join();return 0;
}

如果某个线程在比较和交换时发现 atomic_var 的值已经被其他线程更改,那么它会允许原子变量的值被其他线程更改,并返回 false,表示失败。因此,上面代码输出结果可能会是以下两种情况之一:

结果1

线程 1 比较和交换成功,原子变量的值已修改为: 20

线程 2 比较和交换失败,原子变量的值未修改

结果2

线程 2 比较和交换失败,原子变量的值未修改

线程 1 比较和交换成功,原子变量的值已修改为: 20

compare_exchange_strong 函数

  • 比较和交换不成功时,拒绝其他线程对原子变量的更改,并返回 false,表示失败。
  • 比较和交换成功时,将原子变量的值设置为新值,并返回 true,表示成功。
std::atomic<int> atomic_var(10);void increment(std::atomic<int>& var)
{int expected = 10;int new_value = 20;bool success = var.compare_exchange_strong(expected, new_value);if (success) {std::cout << "线程 " << std::this_thread::get_id()<< std::endl<< " 比较和交换成功,原子变量的值已修改为: " << var << std::endl;} else {std::cout << "线程 " << std::this_thread::get_id()<< std::endl<< " 比较和交换失败,原子变量的值未修改" << std::endl;}
}int main(int argc, char *argv[])
{std::thread t1(increment, std::ref(atomic_var));std::thread t2(increment, std::ref(atomic_var));t1.join();t2.join();return 0;
}

如果某个线程在比较和交换时发现 atomic_var 的值已经被其他线程更改,那么它会拒绝其他线程对原子变量的更改,并返回 false,表示失败。因此,无论哪个线程先执行,只会有一个线程能够成功修改原子变量的值。上述代码输出结果可能会是以下两种情况之一:

结果1

线程 1 比较和交换成功,原子变量的值已修改为: 20

线程 2 比较和交换失败,原子变量的值未修改

结果2

线程 2 比较和交换成功,原子变量的值已修改为: 20

线程 1 比较和交换失败,原子变量的值未修改

2、exchange()

原子地交换原子变量的值,并返回原来的值。

    std::atomic<int> atomic_var(10);// 将 atomic_var 的值交换为 42int old_value = atomic_var.exchange(42);std::cout << "旧值: " << old_value << std::endl;std::cout << "新值: " << atomic_var << std::endl;

3、fetch_add()、fetch_sub()

原子地将原子变量的值增加 / 减少指定的增量,并返回增加前的旧值。

    std::atomic<int> atomic_var(10);// 将 atomic_var 的值原子地增加 5int old_value = atomic_var.fetch_add(5);std::cout << "增加前的值: " << old_value << std::endl;std::cout << "增加后的值: " << atomic_var << std::endl;

4、fetch_and()、fetch_or()、fetch_xor()

原子地将原子变量的值与指定的值进行按位 与 / 或 / 异或 操作,并返回按位与前的旧值。

5、load()、operator T()

获取对象中存储的值,同时确保在多线程环境下进行安全的原子操作(也就是可以安全地在多线程环境下进行,而无需额外的同步控制)。

std::atomic<int> atomicInt(42);
int value = atomicInt.load();//value = 42;

6、store()

用于将给定的值存储到std::atomic对象中,并确保在多线程环境下进行安全的原子操作。该函数没有返回值,它仅负责将值存储到std::atomic对象中。它确保在存储新值之前,不会发生其他线程访问该std::atomic对象的竞争条件。

三、一个自定义类型的示例

std::atomic 不仅支持数值类型,还支持其他可赋值类型。

#include <iostream>
#include <thread>
#include <atomic>
#include <string>struct Person {std::string name;int age;
};std::atomic<Person> atomicPerson;void updatePerson() {Person p{"Alice", 25};atomicPerson.store(p);
}void printPerson() {Person p = atomicPerson.load();std::cout << "Name: " << p.name << ", Age: " << p.age << std::endl;
}int main() {std::thread t1(updatePerson);std::thread t2(printPerson);t1.join();t2.join();return 0;
}

四、std::atomic_ref

此模板类可以对各种类型的非原子变量进行原子操作,比如整型、指针、自定义结构等。它允许在需要时对非原子变量进行原子操作

在多线程环境中使用 std::atomic_ref 可以避免对非原子变量进行竞争条件的操作,从而提高线程安全性。

此类也有如上述的成员函数。

例1:

    int value = 42;std::atomic_ref<int> atomicValue(value);atomicValue.store(10, std::memory_order_relaxed);std::cout << "Value is: " << value << std::endl; // 输出 10

这里首先创建了一个普通的整型变量 value,然后使用 std::atomic_ref 类型的 atomicValue 对其进行原子操作。通过 store 函数,将新的值 10 存储到 atomicValue 中,这也直接影响到了原始的变量 value

例2:

#include <iostream>
#include <atomic>
#include <string>struct UserData 
{std::string name;int age;
};int main(int argc, char *argv[])
{UserData user {"Alice", 30};std::atomic_ref<int> atomicAge(user.age);// 在多线程环境中,使用原子操作更新用户的年龄auto updateAge = [&atomicAge](int newAge){atomicAge.store(newAge, std::memory_order_relaxed);};std::thread t1(updateAge, 35);std::thread t2(updateAge, 40);t1.join();t2.join();std::cout << "Updated user1's age: " << user.age << std::endl; // 输出更新后的年龄return 0;
}

这里使用 std::atomic_ref 类型的 atomicAge 对 user 结构体中的 age 成员变量进行原子操作。在 updateAge 函数中,将新的 age 值存储到 atomicAge 中,并利用 std::thread 创建了两个线程来更新 user 的 age 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/418494.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯练习题-穷举模拟

&#x1f4d1;前言 本文主要是【穷举模拟】——蓝桥杯练习题-穷举模拟的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;…

Spring DI

目录 什么是依赖注入 属性注入 构造函数注入 Setter 注入 依赖注入的优势 什么是依赖注入 依赖注入是一种设计模式&#xff0c;它通过外部实体&#xff08;通常是容器&#xff09;来注入一个对象的依赖关系&#xff0c;而不是在对象内部创建这些依赖关系。这种方式使得对象…

基于C++11的数据库连接池【C++/数据库/多线程/MySQL】

一、概述 概述&#xff1a;数据库连接池可提前把多个数据库连接建立起来&#xff0c;然后把它放到一个池子里边&#xff0c;就是放到一个容器里边进行维护。这样的话就能够避免数据库连接的频繁的创建和销毁&#xff0c;从而提高程序的效率。线程池其实也是同样的思路&#xf…

OCR识别网络CRNN理解与Pytorch实现

CRNN是2015年的论文“An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition”提出的图像字符识别网络&#xff0c;也是目前工业界使用较为广泛的一个OCR网络。论文地址&#xff1a;https://arxiv.org/…

OpenHarmony AI框架开发指导

一、概述 1、 功能简介 AI业务子系统是OpenHarmony提供原生的分布式AI能力的子系统。AI业务子系统提供了统一的AI引擎框架&#xff0c;实现算法能力快速插件化集成。 AI引擎框架主要包含插件管理、模块管理和通信管理模块&#xff0c;完成对AI算法能力的生命周期管理和按需部…

检索增强(RAG)的方式---重排序re-ranking

提升RAG&#xff1a;选择最佳嵌入Embedding&重排序Reranker模型 检索增强生成(RAG)技术创新进展&#xff1a;自我检索、重排序、前瞻检索、系统2注意力、多模态RAG RAG的re-ranking指的是对初步检索出来的候选段落或者文章&#xff0c;通过重新排序的方式来提升检索质量。…

红包封面免费送1000个,你设计,我出额度

相信最近大家或多或少都知道了吧&#xff0c;腾讯又又又给大家&#xff0c;准确的说是给一年勤奋的公众号/视频号博主一个福利 根据不同博主的粉丝、更新频度以及作品质量&#xff0c;给力博主们免费制作红包封面的福利 比如我这个号&#xff0c;有6000额度 那这6000个&#…

从规则到神经网络:机器翻译技术的演化之路

文章目录 从规则到神经网络&#xff1a;机器翻译技术的演化之路一、概述1. 机器翻译的历史与发展2. 神经机器翻译的兴起3. 技术对现代社会的影响 二、机器翻译的核心技术1. 规则基础的机器翻译&#xff08;Rule-Based Machine Translation, RBMT&#xff09;2. 统计机器翻译&am…

【STM32】STM32学习笔记-I2C通信协议(31)

00. 目录 文章目录 00. 目录01. I2C简介02. I2C主要特点03. I2C硬件电路04. I2C时序基本单元05. I2C时序波形图06. 附录 01. I2C简介 I2C(Inter&#xff0d;Integrated Circuit)总线是一种由NXP&#xff08;原PHILIPS&#xff09;公司开发的两线式串行总线&#xff0c;用于连接…

【高等数学之定积分】

一、什么是定积分? 我们第一次新手司机开车从某一地方到(一数)家&#xff0c;自始至终保持着匀速行驶&#xff0c;那么这个过程所经历的路程是什么呢?用速度-时间函数图像来表示一下&#xff0c;我们发现其实就是其曲线下的面积。 第二次开车我们已经有了一定的经验&#xff…

vivado 定义板文件板

定义板文件板 &#xff1c;board&#xff1e;标记是板文件的根。它包括识别基本信息的属性关于董事会。 <board schema_version"2.1" vendor"xilinx.com" name"kc705" display_name"Kintex-7 KC705 Evaluation Platform" url&qu…

数据结构小项目----通讯录的实现(这里用链表实现) 超详细~~~~૮(˶ᵔ ᵕ ᵔ˶)ა

目录 Contact.h说明&#xff1a; 结构体与头文件的包含&#xff1a; ​编辑 函数在头文件的声明与定义&#xff1a; Contact.c中各个函数的实现&#xff1a; 1.检查链表中的数据是否满了&#xff0c;满了就扩容 2.链表的尾插 3.链表的删除 4.查找名字是否匹配 5.初始化通讯…