理解原子操作与CAS锁

news2024/10/7 5:27:59

理解原子操作与CAS锁实现

  • 线程间内存访问同步的问题
  • 理解cpu的存储体系结构
    • cpu存储架构
    • cache line
    • 了解一下写回策略 write-back
    • 多线程运行在cpu的多核之中,数据怎么共享,怎么同步?
      • 通过事件串行化
      • 通过MESI
  • 原子操作
  • CAS锁

线程间内存访问同步的问题

先看一小段程序

#include <chrono>
#include <iostream>
#include <thread>

#define USE_ATOMIC 1
const int test_times = 100;

#if USE_ATOMIC
    #include <atomic>
    std::atomic<int> count{0};
#else
    int count = 0;
#endif

void increase(int num) {
    for (int i = 0; i < num; i++) {
    #if USE_ATOMIC
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        count.fetch_add(1);
    #else
        ++count;
    #endif
    }
}

int main() {

    for(int i = 0; i < test_times ; i++) {
    #if USE_ATOMIC
        count.store(0);
    #else
        count = 0;
    #endif
        std::thread t1(increase, 500);
        std::thread t2(increase, 500);
        std::thread t3(increase, 500);
        std::thread t4(increase, 500);
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    #if USE_ATOMIC
        std::cout << "count.load() =  " << count.load() << std::endl;
        if (count.load() != 2000) {
    #else
        if (count != 2000) {
    #endif
            std::cout << "i: " << i << " count :" << count << std::endl;
            break;
        }

    }
    return 0;
}

USE_ATOMIC 1 程序运行完后,直接退出:说明每次创建的t1,t2,t3,t4四个线程都能把原子变量std::atomic count,增加到2000,程序执行100次,一次失误都没有
USE_ATOMIC 0 程序输出 i: 0 count :1298:说明没有使用原子变量的情况下,i的值是0说明程序只跑了一次,就出了问题,count并没有通过t1,t2,t3,t4四个线程增加到2000。

通过这个例子只想说明,在多线程的程序,对变量的访问存在内存同步的问题(t1,t2,t3,t4四个线程对普通变量 int count 的修改,并没有很好的同步给彼此)。

怎么理解这种现象呢?---- 需要理解多核cpu的存储体系结构。

理解cpu的存储体系结构

cpu存储架构

cpu的速度非常快,内存较慢,为了解决cpu与内存之间速度不匹配的问题,在cpu 与内存之前加了很多级缓存。
类似于写磁盘的操作,写之前也会先写到cache buffer,然后再由cache buffer写入磁盘。
在这里插入图片描述
上图是一个双核的cpu
锁读内存相关的总线: cache line (缓存行,大小为64字节,在不同架构和系统中可能会有所变化)
cache L1,L2 是cpu每个核心独享的, L3是所有核心共享的cache
cpu访问内存的顺序如下:
L1—> L2 —> L3 ----> 主存(内存)
先在L1中找,没有找到,去L2找,没有找到去L3找,没有找到去主存找,读到数据之后,
又会反过来写入到缓冲中去,等一下次再需要读数据的时候,就可以直接从缓存中取了。
但是缓存的都是比较小的,里面会不停的有数据的进入与淘汰,谁负责这些呢?MESI协议,LRU策略等。

cache line

在这里插入图片描述
cache line, cpu从缓存中读数据的基本单元
flag| tag | data: flag判断缓存是否失效 (存MESI 的状态);tag:数据存在哪个地方; data是数据48byte

了解一下写回策略 write-back

cpu要写一个数据,先根据tag, 判断缓存是否命中
1 命中直接写,标记脏数据(直接写缓存)
2 没有命中,缓存里没有值:先在缓存中定位一个缓存块cache line
a 脏数据(数据还没有写到内存里), 把数据写回内存(通过LRU策略淘汰掉的数据才写回内存)
b 数据尽量停留在缓存里,数据在缓存里被淘汰了,才写回内存
c 内存与缓存里的数据一样,不标记脏数据

数据尽量停留在缓存里 这是核心思想,只有这样cpu取数据、指令会快一些

多线程运行在cpu的多核之中,数据怎么共享,怎么同步?

通过事件串行化

假如有t1,t2两个线程对i进行写,t3对i进行读,t1,t2写完的顺序不一样,就会导致t3读到数据可能与最终的结果不一致。
这时可以通过锁指令,分别对t1,t2上锁,确保t3读到正确的值。
但是如果每一次,都广播给其它的核心,代价较大,有可能浪费带宽(不是所有的核心都需要i),这里引入MESI来解决问题。

通过MESI

MESI是一种缓存一致性协议,用于在多处理器系统中保持数据的一致性。它定义了四种状态:Modified(修改),Exclusive(独占),Shared(共享)和Invalid(无效)。当一个处理器访问某个内存位置时,它将读取该位置的值,并将其保存在缓存中。此时,该缓存行的状态被标记为“独占”,表示该处理器是唯一拥有该数据的。如果另一个处理器需要访问相同的内存位置,则必须通过总线请求缓存行。如果该缓存行的状态为“共享”,则该处理器可以直接从缓存中读取数据,而不需要访问主内存。如果该缓存行被标记为“修改”状态,则表示当前处理器已经对其进行了修改,并且需要向其他处理器发送通知,以便它们更新自己的副本或者使其失效。

使用MESI协议可以确保多个处理器之间共享数据时,各自拥有最新版本,并且避免了数据冲突和不一致性问题。

原子操作

1 对于单处理,单核心的机器,一个操作(三条指令)要保持它的原子性,就是确保这三条指令执行的时候不被打断就行。
在执行这三条指令之前屏蔽掉中断,执行完,恢复中断即可。

2 对于多处理器多核心的机器(不同处理间有高速互联总线HSIB ),确保1的同时还需要做到:
以往的处理方式是锁住HSIB,确保原子操作的时候,两个处理器之前不交换数据
现在是通过lock指令,阻止其他核心对相关内存空间的访问

CAS锁

CAS锁(Compare-and-Swap Lock)是一种乐观锁,也被称为无阻塞算法。它基于CPU提供的原子指令实现,用于解决并发环境下对共享资源的竞争访问问题。

CAS锁操作包括三个参数:内存位置V、期望值A和新值B。当V中的值等于A时,将V中的值修改为B,并返回true;否则不修改V中的值,并返回false。通过多次调用CAS操作,可以实现并发环境下对某个共享变量进行安全地读写。

使用CAS锁需要注意以下几点:

1 CAS操作是原子性的,但不能保证在高并发环境下绝对安全;
2 在使用CAS锁时,需要确保所有线程都使用相同的期望值A;
3 如果期望值A与当前内存位置V中的值不匹配,则需要重新尝试操作直到成功。

常用到的方法
compare_exchange_strong ------------ 会阻塞cpu, 会慢一些
compare_exchange_weak --------------- 有可能失败,性能高, 可以加while直到它成功
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接

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

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

相关文章

chatgpt赋能python:如何去除Python列表中的中括号

如何去除Python列表中的中括号 在Python中&#xff0c;列表是一个非常重要的数据类型。它可以存储多个不同类型的元素&#xff0c;并且可以动态地增加或删除元素。但是&#xff0c;有时候我们需要将列表中的元素取出来&#xff0c;而不想要中括号。本文将介绍两种不同的方法来…

.net framework 命令行项目使用 sqlite,DbContext

文章目录 背景描述实际操作环境安装Nuget包安装三者之间的关系ORM解释 项目从零开始创建过程新建项目安装nugetDB first和Code first新建文件注意&#xff0c;DbContext命名运行测试基础增删改查和原生sql操作查增&#xff0c;删&#xff0c;改增改删 sql语句执行sql查询sql执行…

QFIELD在遥感数据处理中的应用

概述 从卫星图像生成的土地覆盖绘图需要地面实况数据&#xff0c;以便“训练”分类算法&#xff0c;并计算最终地图的准确性。地面实况数据由具有准确位置的数据点和来自已知土地覆盖类型的预定义列表的标签组成。本教程将指导用户完成斐济苏瓦训练区的地面实况数据收集。 收…

chatgpt赋能python:Python反向画圆的方法

Python反向画圆的方法 介绍 Python是一种高级编程语言&#xff0c;在数据科学、机器学习、Web开发等领域具有广泛的应用。其中&#xff0c;Python的图形绘制能力也颇为强大&#xff0c;可以利用Python编写出各种图形和动画效果。本文将介绍Python如何反向画圆&#xff0c;通过…

chatgpt赋能python:Python如何去除空行?

Python如何去除空行&#xff1f; 在Python编程中&#xff0c;经常会遇到需要去除文本文件或字符串中的空行的情况。空行通常是在文件或字符串中不希望存在的&#xff0c;因为它们可能会影响代码的可读性和执行效率。但是&#xff0c;如何才能在Python中高效地去除空行呢&#…

CSS布局模式之Flex布局Grid布局

文章目录 1. 简介1.1 什么是CSS布局&#xff1f;1.2 为什么使用CSS布局&#xff1f; 2. Flex布局2.1 什么是Flex布局&#xff1f;2.2 Flex容器和Flex项目2.3 Flex容器的属性2.3.1 flex-direction2.3.2 justify-content2.3.3 align-items2.3.4 align-content 2.4 Flex项目的属性…

计算机视觉-目标检测(一):从 R-CNN 到 Faster R-CNN

文章目录 1. 概要2. 区域卷积卷积神经网络R-CNN2.1 模型结构2.2 Selective Search2.3 warp2.4 R-CNN训练2.5 R-CNN推理2.6 R-CNN性能评价2.7 R-CNN的缺点 3. SPP-Net3.1 SPP-Net对RCNN的改进3.2 SPP-Net网络结构3.3 SPP-Net训练过程3.4 SPP-Net的问题 4. Fast R-CNN4.1 Fast R-…

浅析设计模式 -- 责任链模式

目录 前言 概述 基本概念 ▐ 结构 ▐ 使用 使用示例 ▐ 代码实现​​​​​​​ ▐ 结果输出 ▐ UML图 扩展 源码赏析 优缺点及适用场景 ▐ 优点 ▐ 缺点 ▐ 适用场景 前言 我们在进行软件开发时要想实现可维护、可扩展&#xff0c;就需要尽量复用代码&…

【数据结构与算法】深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

Shell脚本学习记录(常见指令)

Shell echo命令 Shell 的 echo 指令与 PHP 的 echo 指令类似&#xff0c;都是用于字符串的输出。命令格式&#xff1a; echo string 1.显示普通字符串: echo "It is a test" //双引号省略效果相同 2.显示转义字符 echo "\"It is a test\"" …

chatgpt赋能python:Python取出字典中键名对应的值

Python取出字典中键名对应的值 作为一个有10年Python编程经验的工程师&#xff0c;我经常遇到需要从字典中取出键名对应的值的情况。在这篇文章中&#xff0c;我将介绍Python中几种不同的方法来执行这个任务&#xff0c;并提供有关每个方法的优缺点的评估。让我们开始吧&#…

chatgpt赋能python:Python怎么取余数?

Python怎么取余数&#xff1f; 在Python中&#xff0c;我们经常需要进行数学运算&#xff0c;而其中计算余数也是经常用到的。虽然计算余数的方法似乎很简单&#xff0c;但是在不同的编程语言中有些微小的差异。本文将介绍在Python中如何高效地计算余数&#xff0c;以及一些相…

【动态规划】NK刷题记之DP6 连续子数组最大和(C语言实现)

【动态规划】NK刷题记之DP6 连续子数组最大和&#xff08;C语言实现&#xff09; 一、题目二、题解 2.1动态规划2.2贪心算法2.1.1 贪心算法的定义2.2.2贪心算法的性质2.2.3本题的贪心算法解决思路 2.2.4贪心与动态规划的区别 三、代码实现 3.1法一&#xff1a;动态规划(递归实…

无需公网IP,在家使用IPV6和电信光猫进行内网穿透以搭建远程主机

ipv4的公网IP弄起来还是比较麻烦&#xff0c;所以不管是搭建私人NAS还是远程登陆主机都总是需要进行内网穿透。一般的方案都是用花生壳这类的商用服务&#xff0c;然而这些服务一方面又贵又慢还有流量限制&#xff0c;另一方面还要进行把三代信息都盘出去的实名认证 1G到5G一个…

System V通信

文章目录 共享内存什么是共享内存&#xff08;物理内存块属性&#xff09;共享内存的接口认识查看共享内存删除共享内存共享内存的创建&#xff08;ftok和shmget&#xff09;挂接和去关联&#xff08;shmat和shmdt&#xff09; 利用共享内存通信&#xff08;简单的代码演示&…

Vue中如何进行数据缓存

Vue中如何进行数据缓存 Vue是一款流行的前端框架&#xff0c;它提供了许多方便的功能来处理数据。其中一个非常有用的功能是数据缓存。数据缓存可以提高应用程序的性能&#xff0c;减少网络请求&#xff0c;提高用户体验。在本文中&#xff0c;我们将介绍Vue中如何进行数据缓存…

chatgpt赋能python:Python如何取三位小数

Python 如何取三位小数 Python 是一种很强大的编程语言&#xff0c;可以应用于各个领域。其中&#xff0c;处理数字也是 Python 的一项强大功能。当我们需要对数字进行精细的操作时&#xff0c;常常需要使用到取小数的功能。本文将介绍如何使用 Python 取三位小数&#xff0c;…

Qgis中进行Shp和Excel属性连接实现百强县公共预算空间分析

前言 在之前的博文中&#xff0c;将2022的全国百强县一般公共预算收入的数据下载到了本地&#xff0c;博客原文地址&#xff1a;一种使用Java的快速将Web中表格转换成Excel的方法。对于不关注时空位置关系的一般分析&#xff0c;到此也就基本够用了。但是&#xff0c;如果站在全…

C语言函数初阶(1)

目录 1. 函数是什么 2. 库函数 3. 自定义函数 4. 函数参数 5. 函数调用 6. 函数的嵌套调用和链式访问 7. 函数的声明和定义 8. 函数递归 今天我们讲解前6个部分&#xff0c;下一个博客我们讲解后2个部分&#xff0c;因为后两个部分难度较大&#xff0c;讲解起来要花一点…

Vue中如何进行错误处理

Vue中如何进行错误处理 在Vue应用程序中&#xff0c;错误处理是必不可少的。错误可能发生在各种地方&#xff0c;例如网络请求、组件生命周期钩子函数、计算属性、方法等等。如果我们不正确地处理这些错误&#xff0c;可能会导致应用程序崩溃或无法正常工作。在本文中&#xf…