22.C++中的原子操作

C++中的原子操作

文章目录

  • C++中的原子操作
    • atomic_flag
    • `atomic<bool>`
    • `atomic<T*>`
    • 总结
      • reference


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


C++的原子操作也是为了解决多线程编程中同步的问题,它保证在执行原子操作时不会被其他线程干扰。原子操作分为原子赋值和原子递增/递减操作。

原子赋值操作一般用于初始化一个共享资源,确保在多个线程同时访问时,不会出现多个线程同时修改同一个资源的情况。

原子递增/递减操作一般用于计数器,确保在多个线程同时访问时,不会出现多个线程同时修改同一个计数器的情况。

atomic_flag

std::atomic_flagC++中的一个原子布尔类型,用于实现原子锁操作。默认情况下,它是清除状态(false)。可以使用ATOMIC_FLAG_INIT宏进行初始化。std::atomic_flag类型的对象必须由宏ATOMIC_FLAG_INIT初始化,它把标志初始化为置零状态:std::atomic_flag f=ATOMIC_FLAG_INITstd::atomic_flag对象永远以置零状态开始,别无他选。

std::atomic_flag对象只能执行3种操作:销毁、置零、读取原有的值并设置标志成立。这分别对应于析构函数成员函数clear()成员函数test_and_set()

clear()是存储操作,因此无法采用std::memory_order_acquirestd::memory_order_acq_rel内存次序,test_and_set()是“读-改-写”操作,因此能采用任何内存次序,对于上面两种操作,默认的内存序都是最严格的std::memory_order_seq_cst

使用atomic_flag的一个示例:

#include <iostream>
#include <atomic>
#include <thread>std::atomic_flag flag = ATOMIC_FLAG_INIT;void taskFunc(int tid)
{while(flag.test_and_set(std::memory_order_acquire)) { } // 相当于上锁std::cout << "Thread " << tid << " acquired the lock" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Thread " << tid << " released the lock" << std::endl;flag.clear(std::memory_order_release); // 相当于解锁
}int main(int argc, char **argv)
{std::thread t1(taskFunc, 1);std::thread t2(taskFunc, 2);t1.join();t2.join();return 0;
}

通过上面的例子,很容易发现可以借助atomic_flag实现自旋锁:

class SpinLockMutex
{std::atomic_flag flag_{ATOMIC_FLAG_INIT};
public:void lock(){while (flag_.test_and_set(std::memory_order_acquire)){/* spin */}}void unlock(){flag_.clear(std::memory_order_release);}
};

借用实现的自旋锁,重新实现的代码为:

#include <iostream>
#include <atomic>
#include <thread>
#include <mutex>SpinLockMutex sl_mutex;void taskFunc(int tid)
{sl_mutex.lock();std::cout << "Thread " << tid << " acquired the lock" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Thread " << tid << " released the lock" << std::endl;sl_mutex.unlock();
}int main(int argc, char **argv)
{std::thread t1(taskFunc, 1);std::thread t2(taskFunc, 2);t1.join();t2.join();return 0;
}

还可以结合std::lock_guard<SpinLockMutex> lock(sl_mutex);使用。

atomic<bool>

std::atomic_flag操作严格受限,甚至不支持单纯的无修改查值操作,无法用作普通的布尔标志,因此最好还是使用std::atomic<bool>

原子类型的一个常见模式,它们所支持的赋值操作符不返回引用,而是按值返回(该值属于对应的非原子类型)。

通过store()atomic<bool>也能设定内存次序语义。

相较于std::atomic_flagstd::atomic <bool>提供了更通用的成员函数exchange()以代替test_and_set(),它获取原有的值,还让我们自行选定新值作为替换。

std::atomic<bool>还支持单纯的读取(没有伴随的修改行为):隐式做法是将实例转换为普通布尔值,显式做法则是调用load()

总结一下就是:

  • store()是存储操作,
  • load()是载入操作
  • exchange()是“读-改-写”操作

使用atomic<bool>的一个示例:

#include <iostream>
#include <atomic>
#include <thread>
#include <mutex>
#include <functional>class Task {public:Task() {task_runing_.store(true);std::function<void(int)> f = std::bind(&Task::run, this, std::placeholders::_1);task_thread_ = std::thread(f, 1);}~Task() {task_thread_.join();task_runing_.store(false);}void stop(){task_runing_.store(false);}void restart(){task_runing_.store(true);}void run(int id){std::cout << "Running " << task_runing_.load() << std::endl;while (task_runing_.load()){std::cout << "Task " << id << " is running..." << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(1000));}            }private:std::atomic<bool> task_runing_;std::thread task_thread_;
};int main(int argc, char **argv)
{auto t11 = Task();std::cout << "t11 start" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(2000));t11.stop();std::cout << "t11 stop"  << std::endl;t11.restart();std::cout << "t11 restart"  << std::endl;return 0;
}

atomic<T*>

指向类型T的指针的原子化形式为std::atomic<T*>,类似于原子化的布尔类型std::atomic<bool>,二者接口相同,但操作目标从布尔类型变换成相应的指针类型。

std::atomic<T*>std::atomic<bool>一样,也不能拷贝复制或拷贝赋值。

std::atomic<T*>具备成员函数:

  • is_lock_free()
  • load()
  • store()
  • exchange()
  • compare_exchange_weak()
  • compare_exchange_strong()

除此之外,std::atomic<T*>提供的新操作是算术形式的指针运算:

  • **fetch_add()**对象中存储的地址进行原子化加
  • **fetch_sub()**对象中存储的地址进行原子化减
  • 该原子类型还具有包装成重载运算符的+=−=,以及++−−的前后缀版本

实例


#include <iostream>
#include <atomic>
#include <cassert>
#include <thread>
#include <mutex>
#include <functional>class ParalLink
{   public:ParalLink() {}~ParalLink() {Node* current = m_head.load(std::memory_order_relaxed);while (current) {Node* next = current->next;delete current;current = next;}}void insert(int value) {Node* newNode = new Node{value, nullptr};newNode->next = m_head.load(std::memory_order_relaxed);while (!m_head.compare_exchange_weak(newNode->next, newNode, std::memory_order_release)) {}}void printList() {for (auto& t : m_threads) {t.join();}Node* current = m_head.load(std::memory_order_relaxed);while (current) {std::cout << current->data << " ";current = current->next;}std::cout << std::endl;}void taskFunc(int n){for (int i = 0; i < n; ++i) {insert(i*n);}std::this_thread::sleep_for(std::chrono::milliseconds(100));}void build() {std::function<void(int)> f = std::bind(&ParalLink::taskFunc, this, std::placeholders::_1);for(int i = 1; i < 4; i++) {m_threads.emplace_back(f, 4);}}private:std::atomic<Node*> m_head{nullptr};std::vector<std::thread> m_threads;
};int main(int argc, char** argv)
{auto pt = ParalLink();pt.build();pt.printList();return 0;
}

上面的代码使用了atomic<T*>类型的原子模板,这样保护的是Node*类型的指针,而不是Node类型的结构体。如此,对Node*类型的指针m_head的操作就是原子化的了。假如,多个线程通过对指针变量的修改,导致同时发生多线程同时写同块内存的操作,程序会发生什么样的结果呢?想必依然会发生未定义的错误。

总结

std::atomic<>是一个模板,除了前面介绍的bool外还支持int/unsigned short/char等多种类型特化。

原子操作在多线程编程中是非常有用的,可以帮助我们避免很多问题,但是原子操作也有一定的代价,它可能会影响程序的性能。所以,在程序中使用原子操作时,需要根据实际情况,权衡性能与并发性的关系。


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


reference

1.https://en.cppreference.com/w/cpp/atomic/atomic

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

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

相关文章

Day24|回溯算法part01:理论基础、77. 组合

理论基础 回溯法&#xff0c;一般可以解决如下几种问题&#xff1a; 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合切割问题&#xff1a;一个字符串按一定规则有几种切割方式子集问题&#xff1a;一个N个数的集合里有多少符合条件的子集排列问题&#xff1a;N个数…

记录数据库系统时间和服务器时间有差的问题

问题&#xff1a; 在本月开发的时候&#xff0c;有一个问题&#xff0c;就是数据库时间和服务器时间不一致导致定时任务计数一直有问题。 数据库时间别当前时间慢2分钟 服务器时间和当前时间一致 现象&#xff1a; 在我们操作一个业务的时候&#xff0c;操作完&#xff0c;要…

如何通过vscode连接到wsl

下载wsl扩展 远程连接模式

Java方法重写:超越继承的界限

引言&#xff1a; 在面向对象编程&#xff08;OOP&#xff09;的世界里&#xff0c;方法重写&#xff08;Overriding&#xff09;是一个强大的特性&#xff0c;它允许子类改变或扩展继承自父类的方法的行为。Java中的方法是面向对象设计的核心&#xff0c;而方法重写则是实现多…

go的通信Channel

go的通道channel是用于协程之间数据通信的一种方式 一、channel的结构 go源码&#xff1a;GitHub - golang/go: The Go programming language src/runtime/chan.go type hchan struct {qcount uint // total data in the queue 队列中当前元素计数&#xff0c;…

Unity中控制帧率的思考

如何控制帧率&#xff1a; 在Unity中&#xff0c;你可以通过设置Application.targetFrameRate来限制帧率。 例如&#xff0c;如果你想将帧率限制为16帧&#xff0c; 你可以在你的代码中添加以下行&#xff1a; Application.targetFrameRate 16; 通常&#xff0c;这行代码会放在…

专题二_滑动窗口(2)

目录 1658. 将 x 减到 0 的最小操作数 解析 题解 904. 水果成篮 解析 题解 1658. 将 x 减到 0 的最小操作数 1658. 将 x 减到 0 的最小操作数 - 力扣&#xff08;LeetCode&#xff09; 解析 题解 class Solution { public:int minOperations(vector<int>& num…

题目 2906: 笨小猴

题目描述: 笨小猴的词汇量很小&#xff0c;所以每次做英语选择题的时候都很头疼。但是他找到了一种方法&#xff0c;经试验证明&#xff0c;用这种方法去选择选项的时候选对的几率非常大&#xff01; 这种方法的具体描述如下&#xff1a;假设maxn是单词中出现次数最多的字母的…

MPDataDoc类介绍

MPDataDoc类介绍 使用mp数据库新接口mp_api.client.MPRester获取数据&#xff0c;例子如下&#xff1a; from mp_api.client import MPResterwith MPRester(API_KEY) as mpr:docs mpr.summary.search(material_ids["mp-1176451", "mp-561113"])以上代码返…

Java抽象类详解:定义、特性与实例化限制(day12)

抽象类 总结一下今天老师上课的内容&#xff0c;前面几节课听得是有点懵&#xff0c;在讲到内存问题&#xff0c;也就是代码在栈、堆、以及方法区是怎么执行的&#xff0c;听得不是很懂&#xff0c;今天讲到抽象类以及重写的机制&#xff0c;似乎开始慢慢懂得了java的底层原理…

Linux应用实战之网络服务器(三)CSS介绍

0、前言 准备做一个Linux网络服务器应用实战&#xff0c;通过网页和运行在Linux下的服务器程序通信&#xff0c;这是第三篇&#xff0c;介绍一下CSS&#xff0c;优化上一篇文章中制作的HTML页面。 1、CSS常用语法 CSS&#xff08;层叠样式表&#xff09;是用于描述HTML或XML…

FPGA 图像边缘检测(Canny算子)

1 顶层代码 timescale 1ns / 1ps //边缘检测二阶微分算子&#xff1a;canny算子module image_canny_edge_detect (input clk,input reset, //复位高电平有效input [10:0] img_width,input [ 9:0] img_height,input [ 7:0] low_threshold,input [ 7:0] high_threshold,input va…

【案例·增】一条insert语句批量插入多条记录

问题描述&#xff1a; 往MySQL中的数据库表中批量插入多条记录&#xff0c;可以使用 SQL 中的 ((), ()…)来处理 案例&#xff1a; INSERT INTO items(name,city,price,number,picture) VALUES(耐克运动鞋,广州,500,1000,003.jpg),(耐克运动鞋2,广州2,500,1000,002.jpg);规则…

基于java+springboot+vue实现的宠物领养救助平台(文末源码+Lw+ppt)23-363

摘 要 宠物领养救助平台采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从两个对象&#xff1a;由管理员和用户来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对用户、…

Spring Boot 多模块项目跨包自动注入的方法

文章目录 引言I Spring Boot 多模块项目跨包自动注入的方法1.1 问题描述1.2 原因1.3 解决方案1.4 模块结构II Starter项目2.1 多模块项目跨包自动注入2.2 接管生命周期,成为starter2.3 预备知识:自动装配III 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全…

【Redis】redis主从复制

概述 常见的Redis高可用的方案包括持久化、主从复制&#xff08;及读写分离&#xff09;、哨兵和集群。其中持久化侧重解决的是Redis数据的单机备份问题&#xff08;从内存到硬盘的备份&#xff09;&#xff1b;而主从复制则侧重解决数据的多机热备。此外&#xff0c;主从复制…

提高三维模型数据的立体裁剪技术

提高三维模型数据的立体裁剪技术 立体裁剪是三维模型处理中的重要步骤&#xff0c;可以用于去除模型中不需要的部分&#xff0c;提高模型的质量和准确性。本文将介绍几种常见的立体裁剪技术&#xff0c;包括边界裁剪、体素裁剪和几何裁剪&#xff0c;并分析它们的优缺点和适用场…

新朋友+1!拓数派 PieCloudDB Database 与 OpenCloudOS、TencentOS Server 完成产品兼容互认证

近日&#xff0c;拓数派旗下产品云原生虚拟数仓 PieCloudDB Database 与开源操作系统 OpenCloudOS 以及腾讯云旗下操作系统 TencentOS Server 完成了产品兼容性互认证。测试期间&#xff0c;双方产品运行稳定&#xff0c;兼容性良好&#xff0c;功能正常。 随着“数据要素x”三…

交互式RDP服务启停及修改端口的bat脚本

1、执行效果 2、脚本代码 echo off chcp 65001REM 检查是否有管理员权限 net session >nul 2>&1 if %errorlevel% neq 0 (echo 请右键【以管理员身份运行】此脚本。pauseexit /b )REM 提示是否开启或关闭RDP服务 set /p enable_disable是否开启或关闭RDP远程桌面服务…

生成的短链接/二维码,如何更改跳转网址?C1N 短网址一键解决

在当今的营销推广领域&#xff0c;短链接的运用已不可或缺。它能直接将网页、产品或服务呈现在潜在客户或用户面前&#xff0c;提升知名度与曝光率。 然而&#xff0c;使用短链接时也会遭遇一些问题&#xff0c;最常见的便是推广链接已发出&#xff0c;却发现有误或需修改&…