【聊聊原子性,中断,以及nodejs中的具体示例】

news2024/12/26 22:58:13

什么是原子性

从一个例子说起, x++ ,读和写 ,

如图假设多线程,线程1和线程2同时操作变量x,进行x++的操作,那么由于写的过程中,都会先读一份x数据到cpu的寄存器中,所以这个时候cpu1 和 cpu2 拿到了相同的变量x,假设初始x值为1,则cpu1拿到的x为1,cpu2拿到的x为1,都操作并写回给x后,x的值为2。

预期加两次,结果为3,但是实际由于多线程同时操作同一个变量了 ,可能产生写覆盖。进一步看,这其中还要再提起一个词,中断。

中断

多线程 - cpu中断

多线程下,常见一个或者多个操作在 CPU 执行时候,中断,切出再切回。

对于多线程来说,程序在运行一段代码的时候,可能会中途切出,这种来回切出和切回,就出现了上面x++的情况。产生了写覆盖的问题。

那么不用多线程,只用单线程,是不是就不会存在中断的问题,是不是就安全了,其实也不安全。因为线程下面还有协程(如python Coroutine),或如nodejs中 event loop,其虽然不会在cpu运算的时候切出,但是会在等待io的时候切出。

单线程 - io中断

单线程下,一个或者多个IO操作执行的过程中,中断,切出再切回。

一个单线程切出的例子,拿nodejs中event loop举例,worker1 和 worker2分别产生event,去累加result,但是在累加的过程中会await sleep 模拟等待io,这会导致由于等待io而引起的中断,切出。

非原子性示例

function sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

let result = 0;

async function worker1() {
    let maxtime1 = 1;

    while(maxtime1 <= 100) {
        let name = 'worker1';
        // 执行100次)
        console.log(`${name} calculate current time ${maxtime1}`)
        // 开始工作
        let resultCopy = result;
        // 让出
        await sleep(10);
        resultCopy += 1;
        result = resultCopy;

        maxtime1 += 1;
    }
}

async function worker2() {
    let maxtime2 = 1;
    while(maxtime2 <= 100) {
        let name = 'worker2';
        // 执行100次
        console.log(`${name} calculate current time ${maxtime2}`)
        // 开始工作
        let resultCopy = result;
        // 让出
        await sleep(10);
        resultCopy += 1;
        result = resultCopy;

        maxtime2 += 1;
    }
}

(async () => {
    console.log('start calculate')
    const startTime = Date.now();
    Promise.all([worker1(), worker2()]).then(() => {
        const endTime = Date.now();
        // 预期是200 ,但是由于会写覆盖,所以最终小于200.
        console.log(`耗时: ${endTime - startTime}ms`);
        console.log('result:', result);

    }).catch((error) => {
        console.error('A worker failed with error:', error);
    });
})()

运行结果,通过结果 ,甚至输出结果直接就是100,因为worker1 和 worker2的并行执行,导致每次累加计算前,worker1 和 worker2 都拿到相同的值

那么如何避免这种情况,让worker1的代码片段执行完,再执行的worker2的代码片段,不切出,达到原子性,一种方法就是加锁,下面继续看如何加锁达到原子性,

原子性示例

通过加锁,可以实现代码片段的原子性 ,如下

import { Mutex } from 'async-mutex';
const mutex = new Mutex();

function sleep(ms: number) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

let result = 0;

async function worker1() {
    let maxtime1 = 1;

    // 执行100次
    while(maxtime1 <= 100) {
        let name = 'worker1';
        // 开始工作
        // 锁住,
        const release = await mutex.acquire();
        console.log(`${name} calculate current time ${maxtime1}, before start calulate result: ${result}`)

        // r
        let resultCopy = result;
        // 让出cpu,这里即使让出,其它worker由于无法获取锁,所以会一直等待
        await sleep(10);
        resultCopy += 1;
        // w 
        result = resultCopy;

        console.log(`${name} calculate current time ${maxtime1}, after calulate result: ${result}`)
        release();

        maxtime1 += 1;
    }
}

async function worker2() {
    let maxtime2 = 1;
    // 执行100次
    while(maxtime2 <= 100) {
        let name = 'worker2';

        // 开始工作
        // 锁住,
        const release = await mutex.acquire();
        console.log(`${name} calculate current time ${maxtime2}, before start calulate result: ${result}`)
        // r
        let resultCopy = result;
        // 让出cpu
        await sleep(10);
        resultCopy += 1;
        // w 
        result = resultCopy;

        console.log(`${name} calculate current time ${maxtime2}, after calulate result: ${result}`)
        release();

        maxtime2 += 1;
    }
}

(async () => {
    console.log('start calculate')
    const startTime = Date.now();
    Promise.all([worker1(), worker2()]).then(() => {
        const endTime = Date.now();
        // 预期是200 ,但是由于会写覆盖,所以最终小于200.
        console.log(`耗时: ${endTime - startTime}ms`);
        console.log('result:', result);

    }).catch((error) => {
        console.error('A worker failed with error:', error);
    });
})()

此时,在看输出结果,可以发现由于有锁,worker1 和 worker2是串行累加的,不会在执行累加的过程中切出,所以最终累加的结果是200,符合预期。

同时可以发现,由于加锁,整体串行,会导致整体运行时间增加。这里就不得不多提下,Event Loop 是一种异步编程模型,io切出本身属于提高效率的设计,所以如果不是需要原子性,不是同时操作同一个变量,则没必要加锁降低效率。

结语

总结 ,对于编程中的原子性,如果说一段代码是原子性的,则这段代码无论是cpu 还是 io等待 都不能被切出。这段代码需要完整的执行,这才是我们预期的一段代码的原子性。

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

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

相关文章

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol 0. 版本0.1 ERC1967Upgrade.sol 1. 目标合约2. 代码精读2.1 _getImplementation() internal && _upgradeTo(address newImplementation) internal2.2 _upgradeToAndCall(address newImplementation,…

11--ElasticStack7-ELK+Kafka

前言&#xff1a;日志分析管理平台对于平时的规模化运维占的权重非常大&#xff0c;这一章涉及的程序较多&#xff0c;会将每个程序的基础使用和模块分开梳理&#xff0c;基础概念会分布在每小节开头&#xff0c;最后串联成一个完整的工作环境。 1、ELK架构 ELK 是一个非常流…

基于机器学习的永磁同步电机矢量控制策略-高分资源-下载可用!

基于机器学习的永磁同步电机矢量控制策略 优势 训练了RL-Agent&#xff0c;能够提高电机在非线性负载下的性能。 部分程序 仿真结果 转矩估计及dq轴电流。 代码有偿&#xff0c;50&#xff0c;需要的可以联系。

【STM32HAL库学习】通信方式:USART、IIC、SPI

通信的目的&#xff1a;将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统 通信接口区别 名称引脚双工时钟电平设备USARTTX、RX全双工异步单端点对点I2CSCL、SDA半双工同步单端多设备SPISCLK、MOSI、MISO、CS全双工同步单端多设备CANCAN_H、CAN_L半双工异步差分多设…

【antd + vue】表格行合并,同时使用插槽

一、需求说明 表格中&#xff0c;如果一个学校有多个考试科目&#xff0c;则分行展示&#xff0c;其余列&#xff0c;则合并为一行展示&#xff0c;如图所示 二、需求分析 1、表格行合并 相当于有4行&#xff0c;其中1、2行是同一个学校包含不同考试科目及对应人次的数据&am…

COB封装的LED显示屏是什么?

COB&#xff08;Chip on Board&#xff09;封装的LED显示屏&#xff0c;是一种采用先进倒装COB封装技术的显示屏&#xff0c;其中LED芯片是直接被安装并封装在PCB电路板上&#xff0c;而不是先对单个封装再焊接至电路板&#xff0c;与SMD&#xff08;Surface Mount Device&…

Java知识点整理 18 — Lambda表达式

一. 简介 Lambda 表达式是函数式编程思想的体现&#xff0c;强调做什么&#xff0c;而不是以什么方式去做。 面向对象编程思想强调的是对象&#xff0c;必须通过对象的形式来做一些事情。比如多线程执行任务&#xff0c;需要创建对象&#xff0c;对象需要实现指定接口&#x…

【吴恩达机器学习-week2】可选实验:使用 Scikit-Learn 进行线性回归

支持我的工作 &#x1f389; &#x1f4c3;亲爱的朋友们&#xff0c;感谢你们一直以来对我的关注和支持&#xff01; &#x1f4aa;&#x1f3fb; 为了提供更优质的内容和更有趣的创作&#xff0c;我付出了大量的时间和精力。如果你觉得我的内容对你有帮助或带来了欢乐&#xf…

看完这篇文章你就知道什么是未来软件开发的方向了!即生成式AI在软件开发领域的革新=CodeFlying

从最早的UGC&#xff08;用户生成内容&#xff09;到PGC&#xff08;专业生成内容&#xff09;再到AIGC&#xff08;人工智能生成内容&#xff09;体现了web1.0→web2.0→web3.0的发展历程。 毫无疑问UGC已经成为了当前拥有群体数量最大的内容生产方式。 同时随着人工智能技术…

SAP 表字段调整,表维护生成器调整

表维护生成器->已生成的对象->更改->专家模式

Linux下的wifi开发

了解什么是wifi 可参考&#xff1a; 什么是Wi-Fi&#xff1f;Wi-Fi和WLAN的区别是什么&#xff1f; - 华为 (huawei.com) WLAN的基本元素 工作站STA&#xff08;Station&#xff09;&#xff1a;支持802.11标准的终端设备。例如带无线网卡的电脑、支持WLAN的手机等。 接入点AP&…

OpenSSH RCE (CVE-2024-6387) | 附poc | 小试

Ⅰ 漏洞描述 OpenSSH 远程代码执行漏洞(CVE-2024-6387)&#xff0c;该漏洞是由于OpenSSH服务器 (sshd) 中的信号处理程序竞争问题&#xff0c;未经身份验证的攻击者可以利用此漏洞在Linux系统上以root身份执行任意代码。 Ⅱ 影响范围 8.5p1 < OpenSSH < 9.8p1 但OpenSS…

数学建模--层次分析法~~深入解读

目录 1.基本概念 &#xff08;1&#xff09;研究案例 &#xff08;2&#xff09;模型框架 &#xff08;3&#xff09;阐述说明 &#xff08;4&#xff09;注意事项 2.模型的建立和求解 &#xff08;1&#xff09;数量级的统一 &#xff08;2&#xff09;归一化处理 &am…

用Vue3和Rough.js绘制一个粗糙的3D条形图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Rough.js 和 D3.js 绘制粗糙手写风格条形图 应用场景 该代码适用于需要在 Web 应用程序中创建具有粗糙手写风格的条形图的情况。它可以用于数据可视化、信息图表或任何需要以独特和有吸引力的方式呈现数…

Java StringBuffer类和StringBuilder类

在使用 StringBuffer 类时&#xff0c;每次都会对 StringBuffer 对象本身进行操作&#xff0c;而不是生成新的对象&#xff0c;所以如果需要对字符串进行修改推荐使用 StringBuffer。 StringBuilder 类在 Java 5 中被提出&#xff0c;它和 StringBuffer 之间的最大不同在于 St…

【PYG】Cora数据集分类任务计算损失,cross_entropy为什么不能直接替换成mse_loss

cross_entropy计算误差方式&#xff0c;输入向量z为[1,2,3]&#xff0c;预测y为[1]&#xff0c;选择数为2&#xff0c;计算出一大坨e的式子为3.405&#xff0c;再用-23.405计算得到1.405MSE计算误差方式&#xff0c;输入z为[1,2,3]&#xff0c;预测向量应该是[1,0,0]&#xff0…

IAR工程目录移动报错(改变文件目录结构)

刚开始用IAR&#xff0c;记录一下。 工作中使用华大单片机&#xff0c;例程的文件目录结构太复杂了想精简一点。 1.如果原本的C文件相对工程文件&#xff08;.eww文件&#xff09;路径变化了&#xff0c;需要先打开工程&#xff0c;再将所有的.c文件右键Add添加进工程&#xf…

【Godot4.2】Godot中的贝塞尔曲线

概述 通过指定平面上的多个点&#xff0c;然后顺次连接&#xff0c;我们可以得到折线段&#xff0c;如果闭合图形&#xff0c;就可以获得多边形。通过向量旋转我们可以获得圆等特殊图形。 但是对于任意曲线&#xff0c;我们无法使用简单的方式来获取其顶点&#xff0c;好在计…

X-ObjectMount: 对象存储访问接入的新选择

XEOS 自 2017 年发布面世以来&#xff0c;历经 7 年的研发迭代&#xff0c;上个月正式发布了 XSKY SDS 6.4 版本&#xff0c;包含了最新的多站点统一命名空间能力&#xff0c;也标志了 XEOS 在对象存储领域的全方面优势和领先市场地位。 在 XSKY 过去对象存储服务历程里&#…

mysql 命令 —— 查看表信息(show table status)

查询表信息&#xff0c;如整个表的数据量大小、表的索引占用空间大小等 1、查询某个库下面的所有表信息&#xff1a; SHOW TABLE STATUS FROM your_database_name;2、查询指定的表信息&#xff1a; SHOW TABLE STATUS LIKE your_table_name;如&#xff1a;Data_length 显示表…