Atomic-flag-tutorial

本文介绍 <atomic> 头文件中最简单的原子类型: atomic_flagatomic_flag 一种简单的原子布尔类型,只支持两种操作,test_and_setclear

std::atomic_flag 详解

std::atomic_flag 构造函数

std::atomic_flag 构造函数如下:

atomic_flag() noexcept = default;
atomic_flag (const atomic_flag&T) = delete;

std::atomic_flag 只有默认构造函数,拷贝构造函数已被禁用,因此不能从其他的 std::atomic_flag 对象构造一个新的 std::atomic_flag 对象。

如果在初始化时没有明确使用 ATOMIC_FLAG_INIT初始化,那么新创建的 std::atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag 不能被拷贝,也不能 move 赋值。

ATOMIC_FLAG_INIT: 如果某个 std::atomic_flag 对象使用该宏初始化,那么可以保证该 std::atomic_flag 对象在创建时处于 clear 状态。

下面先看一个简单的例子,main() 函数中创建了 10 个线程进行计数,率先完成计数任务的线程输出自己的 ID,后续完成计数任务的线程不会输出自身 ID:

#include <iostream>              // std::cout
#include <atomic>                // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread>                // std::thread, std::this_thread::yield
#include <vector>                // std::vectorstd::atomic<bool> ready(false);    // can be checked without being set
std::atomic_flag winner = ATOMIC_FLAG_INIT;    // always set when checkedvoid count1m(int id)
{while (!ready) {        std::this_thread::yield();} // 等待主线程中设置 ready 为 true.for (int i = 0; i < 1000000; ++i) {} // 计数.// 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.// 此后其他线程执行 test_and_set 是 if 语句判断为 false,// 因此不会输出自身 ID.if (!winner.test_and_set()) {std::cout << "thread #" << id << " won!\n";}
};int main()
{std::vector<std::thread> threads;std::cout << "spawning 10 threads that count to 1 million...\n";for (int i = 1; i <= 10; ++i)threads.push_back(std::thread(count1m, i));ready = true;for (auto & th:threads)th.join();return 0;
}

多次执行结果如下:

atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #6 won!
atomic ) ./Atomic-Flag1     
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #5 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #1 won!
atomic ) ./Atomic-Flag1 
spawning 10 threads that count to 1 million...
thread #10 won!

test_and_set 介绍

std::atomic_flagtest_and_set 函数原型如下:

bool test_and_set (memory_order sync = memory_order_seq_cst) volatile noexcept;
bool test_and_set (memory_order sync = memory_order_seq_cst) noexcept;

test_and_set() 函数检查 std::atomic_flag 标志,如果 std::atomic_flag 之前没有被设置过,则设置 std::atomic_flag 的标志,并返回先前该 std::atomic_flag 对象是否被设置过,如果之前 std::atomic_flag 对象已被设置,则返回 true,否则返回 false

test-and-set 操作是原子的(因此 test-and-set 是原子 read-modify-write (RMW)操作)。

test_and_set 可以指定 Memory Order(后续的文章会详细介绍 C++11 的 Memory Order,此处为了完整性列出 test_and_set 参数 sync 的取值),取值如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_releaseRelease
memory_order_acq_relAcquire/Release
memory_order_seq_cstSequentially consistent

一个简单的例子:

#include <iostream>                // std::cout
#include <atomic>                // std::atomic_flag
#include <thread>                // std::thread
#include <vector>                // std::vector
#include <sstream>                // std::stringstreamstd::atomic_flag lock_stream = ATOMIC_FLAG_INIT;
std::stringstream stream;void append_number(int x)
{while (lock_stream.test_and_set()) {}stream << "thread #" << x << '\n';lock_stream.clear();
}int main()
{std::vector < std::thread > threads;for (int i = 1; i <= 10; ++i)threads.push_back(std::thread(append_number, i));for (auto & th:threads)th.join();std::cout << stream.str() << std::endl;;return 0;
}

执行结果如下:

thread #1
thread #2
thread #3
thread #4
thread #5
thread #6
thread #7
thread #8
thread #9
thread #10

clear() 介绍

清除 std::atomic_flag 对象的标志位,即设置 atomic_flag 的值为 falseclear 函数原型如下:

void clear (memory_order sync = memory_order_seq_cst) volatile noexcept;
void clear (memory_order sync = memory_order_seq_cst) noexcept;

清除 std::atomic_flag 标志使得下一次调用 std::atomic_flag::test_and_set 返回 false。

std::atomic_flag::clear() 可以指定 Memory Order(后续的文章会详细介绍 C++11 的 Memory Order,此处为了完整性列出 clear 参数 sync 的取值),取值如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_releaseRelease
memory_order_acq_relAcquire/Release
memory_order_seq_cstSequentially consistent

结合 std::atomic_flag::test_and_set()std::atomic_flag::clear()std::atomic_flag 对象可以当作一个简单的自旋锁使用,请看下例:

#include <thread>
#include <vector>
#include <iostream>
#include <atomic>std::atomic_flag lock = ATOMIC_FLAG_INIT;void f(int n)
{for (int cnt = 0; cnt < 100; ++cnt) {while (lock.test_and_set(std::memory_order_acquire))  // acquire lock; // spinstd::cout << "Output from thread " << n << '\n';lock.clear(std::memory_order_release);               // release lock}
}int main()
{std::vector<std::thread> v;for (int n = 0; n < 10; ++n) {v.emplace_back(f, n);}for (auto& t : v) {t.join();}
}

在上面的程序中,std::atomic_flag 对象 lock 的上锁操作可以理解为 lock.test_and_set(std::memory_order_acquire); (此处指定了 Memory Order,更多有关 Memory Order 的概念,我会在后续的文章中介绍),解锁操作相当与 lock.clear(std::memory_order_release)

在上锁的时候,如果 lock.test_and_set 返回 false,则表示上锁成功(此时 while 不会进入自旋状态),因为此前 lock 的标志位为 false(即没有线程对 lock 进行上锁操作),但调用 test_and_setlock 的标志位为 true,说明某一线程已经成功获得了 lock 锁。

解锁操作非常简单,直接调用 lock.clear() 即可,此后其他线程可以继续对该 std::atomic_flag 对象(lock)进行上锁和解锁操作。

如果在该线程解锁(即调用 lock.clear(std::memory_order_release)) 之前,另外一个线程也调用 lock.test_and_set(std::memory_order_acquire) 试图获得锁,则 test_and_set(std::memory_order_acquire) 返回 true,则 while 进入自旋状态。如果获得锁的线程解锁(即调用了 lock.clear(std::memory_order_release))之后,另外某个线程试图调用 lock.test_and_set(std::memory_order_acquire) 并且返回 false,则 while 不会进入自旋,此时该线程也成功地获得了锁。

按照上面的分析,我们知道在某种情况下 std::atomic_flag 对象可以当作一个简单的自旋锁使用。

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

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

相关文章

ROS2 学习笔记13:创建 ROS2 包

ROS2 学习笔记13&#xff1a;创建 ROS2 包 Background 背景1 什么是ROS2 包2 ROS2包的组成3 工作空间的包 Prerequisites 前提Tasks 任务1 Create a package2 Build a package3 Source the setup file4 Use the package5 Examine package contents6 Customize package.xml Summ…

虚 拟 化原理

1 概念&#xff1a; ①通俗理解&#xff1a; 虚拟化是在硬件和操作系统之间的实践 ②通过对计算机的服务层级的理解&#xff0c;理解虚拟化概念 抽离层级之间的依赖关系&#xff08;服务器虚拟化&#xff09; 2 虚拟化分类 ①按架构分类 ◆寄居架构&#xff1a;装在操作系统上…

pytest.param作为pytest.mark.parametrize的参数进行调用

pytest.param&#xff1a;在 pytest.mark.parametrize 中可以作为一个指定的参数进行调用 获取数据库&#xff08;网页端&#xff09;数据&#xff0c;通过pytest.param包装成数据包用于pytest.mark.parametrize 中实现数据驱动调用。 import os import pytest import json fr…

OSCP靶场--Nickel

OSCP靶场–Nickel 考点(1.POST方法请求信息 2.ftp&#xff0c;ssh密码复用 3.pdf文件密码爆破) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.237.99 -sV -sC -p- --min-rate 5000 Starting Nmap 7.92 ( https://nmap.org ) at 2024-02-22 04:06 EST Nm…

5.1 Ajax数据爬取之初介绍

目录 1. Ajax 数据介绍 2. Ajax 分析 2.1 Ajax 例子 2.2 Ajax 分析方法 &#xff08;1&#xff09;在网页页面右键&#xff0c;检查 &#xff08;2&#xff09;找到network&#xff0c;ctrl R刷新 &#xff08;3&#xff09;找 Ajax 数据包 &#xff08;4&#xff09;…

【前后端的那些事】文件上传组件封装

文章目录 效果前端代码后端代码组件封装 效果 前端代码 /views/file/file.vue <template><el-row><el-uploadv-model:file-list"fileList"class"upload-demo"multiple:auto-upload"false":on-preview"handlePreview"…

Javascript[ECMAScript] ES6、ES7、ES8、ES9、ES10、ES11、ES12、ES13、ES14[2023]新特性

# 前言 鉴于找不到ES6-ES14 的新特性集合&#xff0c;所以有了这篇文章&#xff0c;后续会持续更新每年的新特性 # 背景 ## JS1.1&#xff08;1997&#xff09; [第一版基于Netscape Navigator 3.0中实现的JAVASCRIPT 1.1](https://web.archive.org/web/19970614042441/http:/…

数据结构之:跳表

跳表&#xff08;Skip List&#xff09;是一种概率性数据结构&#xff0c;它通过在普通有序链表的基础上增加多级索引层来实现快速的查找、插入和删除操作。跳表的效率可以与平衡树相媲美&#xff0c;其操作的时间复杂度也是O(log n)&#xff0c;但跳表的结构更简单&#xff0c…

Swiper.js:不识这个轮播图js库,说明你的前端还未入门

hello&#xff0c;我是贝格前端工场&#xff0c;本期给大家带来轮播图的s库&#xff1a;Swiper.js&#xff0c;用这个类库处理轮播图、幻灯片、画廊那是得心应手&#xff0c;非常的easy&#xff0c;欢迎老铁们点赞关注&#xff0c;如有前端定制开发需求可以私信我们。 一、Swip…

网络编程、UDP、TCP

计算机网络 就是将地理位置不同的具有独立功能的多台计算及外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统、网络管理软件以及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统 目的 传播交流信息、数据交换、通信 如何做…

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理

TensorRT及CUDA自学笔记003 CUDA编程模型、CUDA线程模型及其管理、CUDA内存模型及其管理 各位大佬&#xff0c;这是我的自学笔记&#xff0c;如有错误请指正&#xff0c;也欢迎在评论区学习交流&#xff0c;谢谢&#xff01; CUDA编程模型 我们使用CUDA_C语言进行CUDA编程&am…

【Vue3】‘vite‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

问题 今天拿到别人项目的时候&#xff0c;我平时比较习惯用pnpm&#xff0c;我就使用pnpm i先下载依赖包&#xff0c;下载完成后&#xff0c;启动项目&#xff0c;就开始报以下错误&#xff01; 但是当我执行pnpm i的时候&#xff0c;vite不应该就已经被我下载下来了吗 研究了…

学习JAVA的第五天(基础)

目录 API 字符串 String概述 比较的是什么&#xff1f; 关于字符串的比较 字符串代码展示 StringBuilder概述 StringBuilder方法的代码展示 StringJoiner概述 StringJoiner方法的代码展示​​​​​​​ 练习-用户登录 API API&#xff08;Application Programing I…

【Java程序设计】【C00307】基于Springboot的基Hadoop的物品租赁管理系统(有论文)

基于Springboot的基Hadoop的物品租赁管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的基于 Hadoop的物品租赁系统的设计与实现&#xff0c;本系统有管理员、用户二种角色权限&#xff1b; 前台首页&#…

day11-项目集成SpringSecurity-今日指数

项目集成SpringSecurity 学习目标 理解自定义认证和授权过滤器流程&#xff1b;理解项目集成SprignSecurity流程&#xff1b; 第一章 自定义认证授权过滤器 1、SpringSecurity内置认证流程 通过研究SpringSecurity内置基于form表单认证的UsernamePasswordAuthenticationFi…

【工程院院士加盟】第四届计算机通信与人工智能国际会议

CCAI 2024 | Xian, Chinahttp://ccai.net/ - IEEE出版&#xff0c;EI核心和Scopus检索 - 工程院院士&#xff0c;IEEE Fellow等学术大咖主题演讲 - 会议时间-地点&#xff1a;2024年5月24-26日&#xff0c;中国西安 会议简介 Brief Introduction 作为人工智能的重要传播技术…

Linux环境下基本指令

今天我们一起来认识一下Linux环境下一些基本的指令&#xff0c;这些指令是我们学习Linux的基础&#xff0c;只有掌握了这些指令&#xff0c;我们才能在Linux环境下进一步学习知识&#xff0c;话不多说&#xff0c;我们开始&#xff08;以下演示操作是在云服务器的环境下&#x…

基于SSM的车位租赁系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的车位租赁系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

MySQL TO_DAYS() 函数详解与实际应用

前言 在数据库操作中&#xff0c;日期和时间函数是处理日期相关计算和筛选数据时不可或缺的一部分。MySQL 作为广泛应用的关系型数据库管理系统&#xff0c;提供了丰富的日期时间函数供开发者使用。本文将详细介绍 MySQL 中的 TO_DAYS() 函数&#xff0c;包括其基本功能、用法…

【数据分析之Numpy基础004】数学运算大揭秘:轻松玩转ndarray的强大实力

ndarray的数学运算是一项非常重要的操作&#xff0c;包括不同对象之间的四则运算&#xff0c;三角函数变换、求和、求平均等操作 1、四则运算 如果参与运算的两个对象都是ndarray&#xff0c;并且形状相同&#xff0c;那么就可以进行对位之间的四则&#xff08; - * / &#x…