无锁编程(CAS)-程序员宅基地

技术标签: 高并发  CAS  编程语言学习笔记  无锁编程  

参考高并发之无锁编程

多线程并发

在高并发场景下往往需要用到多线程编程,又由于多个线程共享同一个进程中的地址空间,所以又可能会出现同时访问/修改同一个共享变量的情况,这就涉及到线程安全的问题,比如

  • 两个线程同时修改同一个数据,可能造成某线程的修改丢失;
  • 一个线程写的同时,另一个线程去读该数据时可能会读到写了一半的数据。

对此,我们可以用锁来解决线程安全的问题,在访问共享变量的代码的前后加上加锁和解锁的操作,从而保证同一时刻只有一个线程处于临界区中访问资源。但是除此之外,还有一种无锁编程的方式也可以解决线程安全的问题。

CAS

无锁编程是基于CAS(Compare And Swap)机制实现的,CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,一个经典例子是,CAS对内存地址V的值从A改成B,那么CAS总共有两步操作:

  1. 检查此刻内存地址V处的值是否还是旧值A。
  2. 如果是,将其修改为B;如果不是,说明有其他线程修改了内存地址V处的值,因此放弃修改。

如果修改失败,会继续尝试,这个操作也叫做自旋。由于CAS整体是一个原子操作,因此在CAS修改成功的情况下,能够保证是线程安全的。从思想上看,

  • Mutex 属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守,所有企图访问变量的线程都需要经历加锁解锁的操作(即使没有人跟它竞争)。
  • CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以直接尝试去修改,碰壁的情况下不断自旋尝试。

无锁编程实例

cankao C++性能榨汁机之无锁编程

原子操作是无锁编程的基石,原子操作是不可分隔的操作,一般通过CAS(Compare andSwap)操作实现,CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

C++11的线程库为我们提供了一系列原子类型,同时提供了相对应的原子操作,原子类型的基本使用方法如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <chrono>

using namespace std;
atomic<int> i = 0;

void iplusplus() {
    
    int c = 10000000;  //循环次数
    while (c--) {
    
        i++;
    }
}
int main()
{
    
    chrono::steady_clock::time_point start_time = chrono::steady_clock::now();//开始时间
    thread thread1(iplusplus);
    thread thread2(iplusplus);
    thread1.join();  // 等待线程1运行完毕
    thread2.join();  // 等待线程2运行完毕
    cout << "i = " << i << endl;
    chrono::steady_clock::time_point stop_time = chrono::steady_clock::now();//结束时间
    chrono::duration<double> time_span = chrono::duration_cast<chrono::microseconds>(stop_time - start_time);
    std::cout << "共耗时:" << time_span.count() << " ms" << endl; // 耗时
    system("pause");
    return 0;
}

代码的第8行定义了一个原子类型(int)变量i,在第13行多线程修改i的时候即可免去加锁和解锁的步骤,同时又能保证变量i的线程安全性。代码运行结果如下:

img

可以看到i的值是符合预期的,代码运行总耗时1.12731ms,仅为有锁编程的耗时3.37328ms的1/3,由此可以看出无锁编程由于避免了加锁而相对于有锁编程提高了一定的性能。

无锁编程优缺点

无锁编程的优点:

  1. 访问共享变量只需要一条语句(CAS原子操作),可以节省每次对共享变量进行操作都进行的加锁解锁动作,节省了系统开销。
  2. 并发竞争的时候使用自旋尝试的方法,避免了用锁的情况下线程因阻塞而频繁的切换。

无锁编程的缺点:

  1. 只能对简单的原子类型进行修改,不支持对大型的数据对象进行修改,或者说只能保证一条代码的原子性,不能保证代码块的原子性。
  2. 在并发量比较高的情况下,如果许多线程更新变量不成功,一直自旋反复尝试,会给CPU带来很大的负担。

无锁编程的扩展应用

那么这里就牵涉到一个问题了,在高并发场景下,如何高效地控制多个线程对一个很大的对象的访问呢?

  1. 使用无锁编程:无锁编程只能对简单的原子类型进行修改,不支持对大型的数据对象进行修改。
  2. 使用锁:修改一个很大的对象需要很长的一段代码和处理过程,如果写线程锁住这整个修改过程,那么会导致所有的读线程被阻塞很长一段时间。

当然如果这是一个高一致性的场景,也只能使用锁了。但是如果这并不是一个对一致性要求非常高的场景呢?比如可以允许修改内容在10s内生效而不是立即生效,那么就可以结合无锁编程使用一种高效的方法:

用指针来访问这个共享对象,写线程修改对象时,不直接在对象上进行修改,而是新建一个对象进行赋值,赋值完成后,再通过CAS将指针指向这个新的对象,然后销毁旧的对象。通过这种方法,可以将临界区从一大段代码缩减为一句代码,极大的减少了对读线程的影响。

修改指针这一步只有一条代码语句,虽然也可以用加锁的方式来保证线程安全,但是恰好无锁编程也是只能对一条语句进行操作,所以无锁编程非常符合这种方式,往往一想到无锁编程就想到这种方法。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41519463/article/details/122246636

智能推荐

大地坐标BLH转平面坐标xyh(高斯投影坐标正算) Java版-程序员宅基地

文章浏览阅读1.7k次。技术背景  做过位置数据处理的小伙伴基本上都会遇到坐标转换,而基于高斯投影原理的大地坐标转平面坐标就是其中一种坐标转换,坐标转换的目的就是方便后面数据的处理工作,大地坐标转高斯平面坐标常用的有两种,即3°带和6°带,具体采用哪种根据实际情况而定。计算原理  6°带带号n与相应的中央子午线L0经度的关系为:  3°带带号n’与相应的中央子午线L0’经度的关系为:..._blh转xyh

基于PHP美食食谱的外文翻译,中国传统菜谱的英文翻译锦集-程序员宅基地

文章浏览阅读167次。中国传统菜谱的英文翻译锦集第1部分、素菜类Vegetarian1.豪油冬菇Oyster Sauce Mushroom2.什笙上素Bamboo Vegetable3.红烧豆腐Fried Tofu4.炒素丁Vegetable Roll5.罗汉腐皮卷Vegetable Egg Roll6.素咕噜肉Vegetarian Sweet and Sour7.蒸山水豆腐Steam Tofu8.鲜菇扒菜胆Mushr..._基于php的家常菜谱教程网站的设计与实现 英文翻译

(jarvisOJ)(pwn)level6_x86_javious level6x86-程序员宅基地

文章浏览阅读418次。Unlink!Unlink!Unlink!level6_x86作为一道堆溢出入门题,还是值得用来练练手,学习一下堆的基础的知识的。参考文献:ctf-wiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unlink-zh/大佬博客:https://blog.csdn.net/weixin_41617275/article..._javious level6x86

数据科学与大数据技术与计算机科学与技术哪个好_数据科学与大数据技术和计算机科学与技术-程序员宅基地

文章浏览阅读4.5k次。这两门课,一个是大数据,一个是人工智能 都是现在炙手可热的学科。相对而言,大数据适用性更广一些。人工智能专业,毕业后不是大批用人单位都会有AI研发需求的。智能科学与技术专业和数据科学与大数据技术专业。智能科学与技术专业:科技永远是第一生产力社会在不断进步,科技也在不断发展现在更新换代的速度实在是太快了,但是科学技术是永远不会过时的。智能科学与技术专业,是面向高新技术产业的基础性本科专业知识覆盖面比较广。这个专业会涉及到机器人技术,新一代网络计算为基础的智能系统微机电系统等,对国民经济,工业生产以及日常生活都_数据科学与大数据技术和计算机科学与技术

Unity SRP URP HDRP 的区别_3dhdrp 3durp-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏10次。https://blog.csdn.net/weixin_41622043/article/details/107623694_3dhdrp 3durp

vim 选中文本-程序员宅基地

文章浏览阅读2.1k次。y复制文本,d删除文本,p粘贴文本。移动命令(如:gg、G)h,j,k,l或方向键。d或y等命令删除或复制。_vim 选中

随便推点

【Unity】基于GUI的简易场景切换器_unity切换display-程序员宅基地

文章浏览阅读1k次。在编辑器或者发行版游戏中,如果我们想切换一个场景,需要设置触发器或者手动设置按钮来调用SceneManagement中的函数。而这个脚本挂载到场景时按下按键就可以在屏幕上方或者下方展示所有Build Setting中的场景,按下按键就可以加载对应场景。将脚本挂载到任意物体上,也可以制作成预制体。在编辑器中运行游戏,按下键盘上~键(即上方数字1左边的按键)就可以调出所有场景,再按一下关闭,点击场景按钮切换到指定场景。Button Width:调整按钮宽度,若开启自适应按钮宽度,该项不造成影响,默认100。_unity切换display

简单使用Git和Github来管理自己的代码和读书笔记-程序员宅基地

文章浏览阅读50次。以前不知道使用代码管理工具,最后写的一些东西都没有了,由于硬盘坏了或者不小心格式化了之类的,后来使用了Git和Github来托管自己的代码和读书笔记方便了不少,到哪里只要有网就可以把自己的东西拷贝下来继续使用。我这里简单的记录一下我使用的过程,最简单的使用都是,高级的功能我一直没有使用到,虽然买一本《Git权威指南》但是很多东西用不到就不能够真的会。下面开始简单介绍我使用的方法,我这个...

tf.losses.mean_squared_error函数浅析-程序员宅基地

文章浏览阅读9.6k次,点赞4次,收藏13次。jjjsjs_tf.losses.mean_squared_error

拓扑与代数几何:拓扑学在代数几何中的应用-程序员宅基地

文章浏览阅读21次。1. 背景介绍拓扑学是数学中的一个分支,研究的是空间的性质和变形。而代数几何则是将代数方法应用于几何学中,研究的是代数对象和几何对象之间的关系。这两个领域看似毫不相关,但实际上它们之间有着紧密的联系。在代数几何中,我们经常需要研究代数对象的性质,比如说代数曲线、代数簇等等。而这些代数对象通常都可以看作是某个拓扑空间的子集。因此,拓扑

【Linux系统IO函数】read、write函数及实现文件拷贝_read函数实现-程序员宅基地

文章浏览阅读5.7k次,点赞8次,收藏75次。Linux系统—read、write函数ssize_t read(int fd, void *buf, size_t count);//将文件中的数据读入内存ssize_t write(int fd, const void *buf, size_t count);//把内存中的数据写入到文件里实现文件拷贝:1.1 read函数输入以下命令查看函数帮助文档:man 2 read/write#include <unistd.h>ssize_t read(int fd, v_read函数实现

android 微信小程序 gps 飘,微信小程序实现自动定位功能-程序员宅基地

文章浏览阅读551次。本文实例为大家分享了微信小程序实现自动定位的具体代码,供大家参考,具体内容如下使用了腾讯地图提供的免费api:需要引入一个js文件:下载地址js代码:// 引入SDK核心类var QQMapWX = require('../../libs/qqmap-wx-jssdk.js');var qqmap = new QQMapWX({//在腾讯地图开放平台申请密钥 http://lbs.qq.com/m..._安卓小程序实时定位功能

推荐文章

热门文章

相关标签