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.mzph.cn/news/636853.shtml

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

相关文章

【leetcode】下一个排列 双指针算法

/*** param {number[]} nums* return {void} Do not return anything, modify nums in-place instead.*/ var nextPermutation function(nums) {const len nums.length;let i len -2;//找到第一个当前项比后一项小的位置iwhile(i>0&&nums[i]>nums[i1]) i--;if(…

(202401)深度强化学习基础2:策略梯度

文章目录 前言策略梯度1 基于价值算法的缺点2 策略梯度算法3 REINFORCE算法本章小结 前言 感谢Datawhale成员的开源本次学习内容的文档地址为 第九章 策略梯度 策略梯度 这个章节会开始介绍基于策略梯度的算法。前面的算法都是针对“奖励”或者说“回报&#xff08;reward&a…

章鱼网络 Community Call #17|打造全新 Omnity 跨链协议

香港时间2024年1月8日12点&#xff0c;章鱼网络举行第17期 Community Call。 对于 Octopus Community 而言&#xff0c;2023年是一个分水岭。我们如期兑现我们的承诺&#xff0c;成功上线了包括 $NEAR Restaking 和 Adaptive IBC 在内的完整的 Octopus 2.0。 自从我们在2023年…

9.2 工厂模式(❤)

9.2 工厂模式(❤) 1. 工厂模式介绍1.1 工厂模式1.2 简单工厂1.2.1 案例讲解: 购买电脑2. 工厂模式用途和特点3. 工厂模式项目应用1. 工厂模式介绍 1.1 工厂模式

HashMap原理

Java面试题 HashMap原理 HashMap是数组链表/红黑树结构&#xff0c;在put元素时会经过以下步骤&#xff1a; 1.先根据key的hashcode计算存储索引。 2.如果数组为空&#xff0c;则先执行resize进行扩容。 3.判断是否存在哈希冲突&#xff0c;如果没有则直接生成链表结构Node放入…

Java便捷访问的好处

便捷访问有以下几个好处&#xff1a; 提高效率&#xff1a;便捷访问可以减少查找和获取信息所需的时间和努力&#xff0c;从而提高工作和生活效率。 增强用户体验&#xff1a;提供便捷访问可以使用户感到方便和满意&#xff0c;从而增强他们对产品或服务的体验。 增加使用频率…

springcloud gateway正确路由配置 uri找不到服务?

引言 在集成nacos组件以及springcloudalibaba 的gateway时&#xff0c;出现正确配置&#xff0c;如下文配置&#xff0c;却在测试demo调用该服务的时候&#xff0c;无法路由到此服务&#xff0c;说是无效服务。经过查阅资料得知其命名不符合gateway的命名规范&#xff0c;因而…

Http三种常见状态码的区别(401、403、500)

一、解释 401 Unauthorized&#xff08;未经授权&#xff09;&#xff1a;表示请求需要进行身份验证&#xff0c;但客户端未提供有效的身份验证凭据。通常&#xff0c;当用户尝试访问需要身份验证的资源时&#xff0c;服务器会返回401状态码&#xff0c;以提示客户端提供有效的…

万界星空科技mes系统可以为企业带来什么好处

随着信息技术的不断发展&#xff0c;MES生产制造系统的作用不断凸显。万界星空科技MES生产制造可以为企业带来四个方面的好处&#xff1a;提升生产效率、降低生产成本、优化生产过程、提高生产质量。本文将从这四个方面分别进行详细阐述&#xff0c;旨在通过对MES生产制造系统的…

动态规划汇总

作者推荐 视频算法专题 简介 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;是运筹学的一个分支&#xff0c;是求解决策过程最优化的过程。每次决策依赖于当前状态&#xff0c;又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的&#x…

C#调用C++ dll异常排查

基本情况 最近在做的一款程序&#xff0c;长时间运行总会出现莫名的问题。有时是自动关闭&#xff0c;有时程序报错&#xff0c;有时调用的dll异常…… 提出假设——dll内存泄漏 由于开始与C组合作时&#xff0c;使用其提供的dll出现过数据读写时异常&#xff08;内存操作异常…

线程的创建与回收

目录 一、线程的创建 进程&#xff1a; 线程&#xff1a; 线程特点&#xff1a; 一个进程中的多个线程共享以下资源&#xff1a; 每个线程私有的资源包括&#xff1a; Linux线程库&#xff1a; 线程创建-pthread_create 二、线程的参数传递 线程结束-pthread_exit 线程查…

【论文解读】LERF:语言嵌入的辐射场(ICCV 2023 Oral)

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2303.09553 项目主页&#xff1a;https://lerf.io](https://lerf.io 图 1&#xff1a;语言嵌入辐射场 (LERF)。 LERF 将 CLIP 表示建立在密集、多尺度的 3D 场中。…

Oracle SQL Developer执行sql脚本文件

文件过于大&#xff0c;无法打开&#xff0c;直接在界面执行。 ①将文件放置到D盘&#xff0c;文件名 daochu5.sql ② 在工具执行SQL界面输入 d:\daochu5.sql;,点击运行按钮运行

Shell 脚本实现自动启动程序、日志管理和定时任务监控

简介 本篇将通过Shell 脚本实现自动启动Java程序、日志管理和定时任务监控。脚本启动程序具灵活定制、可移植性和扩展性强的优点&#xff0c;可以根据需要添加额外的功能、配置选项和自定义行为&#xff0c;从而满足更具体的要求。 脚本编写 vim start_program.sh#!/bin/bas…

Swift抓取某网站律师内容并做排名筛选

有个很要好的朋友&#xff0c;今天找我说他的朋友欠他钱&#xff0c;因为工程上面的事情&#xff0c;所以一直没拿到款。想让我找个靠谱的律师帮他打官司&#xff0c;因为这个也不是我的强项&#xff0c;也没有这方面的经验。随即从律师网站爬取对应律师口碑以及成功案例&#…

【第七在线】利用大数据与AI,智能商品计划的未来已来

随着科技的快速发展&#xff0c;大数据和人工智能(AI)已经成为各行各业变革的重要驱动力。在服装行业&#xff0c;这两大技术的结合正在深刻改变着传统的商品计划方式&#xff0c;引领着智能商品计划的未来。 一、大数据与AI在智能商品计划中的角色 大数据为智能商品计划提供了…

【设计模式】腾讯二面:自动贩卖机/音频播放器使用了什么设计模式?

状态模式是什么&#xff1f; 状态模式&#xff0c;也被称作状态对象模式&#xff0c;是一种行为设计模式。 当一个对象的内在状态改变时&#xff0c;允许改变其行为&#xff0c;这个对象看起来像是改变了其类。 它让对象在其内部状态改变时改变自己的行为。外部调用者无需了…

数据结构学习之双向链表(各种操作合集)

双向链表&#xff08;各种操作合集&#xff09; 双向链表的两种创建方式&#xff1a; 方法1&#xff1a;根据函数的返回值创建 通过返回值返回所申请的头结点所在的内存空间首地址&#xff0c;即创建双向链表的头结点&#xff0c;代码如下&#xff1a; 示例代码&#xff1a;…

【设计模式】什么是外观模式并给出例子!

什么是外观模式&#xff1f; 外观模式是一种结构型设计模式&#xff0c;主要用于为复杂系统、库或框架提供一种简化的接口。这种模式通过定义一个包含单个方法的高级接口&#xff0c;来隐藏系统的复杂性&#xff0c;使得对外的API变得简洁并易于使用。 为什么要使用外观模式&a…