当前位置: 首页 > article >正文

Node原子计数器

文章目录

    • 基础
    • Automics
    • Mutex
    • 异常并发 case 非原子
    • 正常操作 case 原子

基础

node 并发node通过单线程来处理高并发的请求。

一个事件循环中的执行是可以保证并发安全的,但是也业务操作并发读写一样会有业务的并发问题

在 JavaScript 中,函数总是运行到完成。这意味着如果一个函数正在运行,那么它将完全运行; 只有在这之后,才会调用另一个函数。因此,语句之间不存在交织的可能性(但是对于 Java 来说就不同了)。

单线程 eventLoop

线程锁:单线程编程模式下请求是顺序的,一个好处是不需要考虑线程安全、资源竞争问题,因此当你进行 Node.js 编程时,也不会去考虑线程安全问题。那么多线程编程模式下,例如 Java 你可能很熟悉一个词 synchronized,通常也是 Java 中解决并发编程最简单的一种方式,synchronized 可以保证在同一时刻仅有一个线程去执行某个方法或某块代码。

进程锁:一个服务部署于一台服务器,同时开启多个进程,Node.js 编程中为了利用操作系统资源,根据 CPU 的核心数可以开启多进程模式,这个时候如果对一个共享资源操作还是会遇到资源竞争问题,另外每一个进程都是相互独立的,拥有自己独立的内存空间。关于进程锁通过 Java 中的 synchronized 也很难去解决,synchronized 仅局限于在同一个 JVM 中有效。

分布式锁:一个服务无论是单线程还是多进程模式,当多机部署、处于分布式环境下对同一共享资源进行操作还是会面临同样的问题。此时就要去引入一个概念分布式锁。如下图所示,由于先读数据在通过业务逻辑修改之后进行 SET 操作,这并不是一个原子操作,当多个客户端对同一资源进行先读后写操作就会引发并发问题,这时就要引入分布式锁去解决,通常也是一个很广泛的解决方案。

分布式锁

Automics

原子性操作

Atomics.add() - JavaScript | MDN

Atomics in JavaScript - GeeksforGeeks


describe('autoMicNumberCount', () => {
    const counter = new Int32Array(new SharedArrayBuffer(4));

    /**
     * @description 任务数量++
     * @private
     */
    async function handCurrentTaskAdd() {
        Atomics.add(counter, 0, 1);
    }

    /**
     * @description 任务数量--
     * @private
     */
    async function handCurrentTaskSub() {
        Atomics.sub(counter, 0, 1);
    }
    
    it('autoMicNumberCount test', async () => {
        const tasks = [];
        for (let i = 0; i < 10000; i++) {
            tasks.push(handCurrentTaskAdd());
        }
        for (let i = 0; i < 9000; i++) {
            tasks.push(handCurrentTaskSub());
        }
        await Promise.all(tasks);
        expect(Atomics.load(counter, 0)).toBe(1000);
    });
});

Mutex


    private readonly mutex = new Mutex();

    private currentTaskComplete = 1;

    /**
     * @description 任务数量++
     * @private
     */
    private async handCurrentTaskAdd() {
        await this.mutex.runExclusive(async () => {
            this.currentTaskComplete++;
        });
    }

    /**
     * @description 任务数量--
     * @private
     */
    private async handCurrentTaskSub() {
        await this.mutex.runExclusive(async () => {
            this.currentTaskComplete--;
        });
    }

异常并发 case 非原子

describe('autoMicNumberCount', () => {
    let a = 1;

    async function one() {
        return 1;
    }

    async function example() {
		    // 操作被分割成了多个步骤,并且由于await one();的存在,中间可能会插入其他操作,这就打破了原子性。
        console.log('Adding 1 to a');
        a += await one();
        // 修改 a++ 最终结果就是一致的
    }
    it('autoMicNumberCount test', async () => {
        console.log(`Start, a = ${a}`);
        Promise.all([
            example(),
            example(),
            example(),
        ])
            .then(() => {
                console.log(`All done, a = ${a}`);
            });
    });
});

正常操作 case 原子

对于JavaScript而言,由于它是单线程的(至少在V8引擎中是这样),因此在没有显式使用异步或并发特性的情况下,函数中的操作通常被认为是原子性的。

下面操作测试结果都是正常的,不过这块代码对于性能也没有特别苛刻要求,自己对底层了解还是不太足够没有特别大的把握,使用Automics放心一点吧

describe('autoMicNumberCount', () => {
    let count = 0;
    /**
     * @description 任务数量++
     * @private
     */
    function handCurrentTaskAdd() {
        count++;
    }

    /**
     * @description 任务数量--
     * @private
     */
    function handCurrentTaskSub() {
        count--;
    }

    it('autoMicNumberCount test', async () => {
        const tasks = [];
        for (let index = 0; index < 10000; index++) {
            tasks.push(handCurrentTaskAdd());
        }
        for (let index = 0; index < 9000; index++) {
            tasks.push(handCurrentTaskSub());
        }
        await Promise.all(tasks);
        expect(count).toBe(1000);
    });
});

describe('autoMicNumberCount', () => {
    let count = 0;

    beforeEach(() => {
        // 在每个测试之前启用假定时器
        jest.useFakeTimers();
    });

    afterEach(() => {
        // 在每个测试之后恢复真实的定时器
        jest.useRealTimers();
    });

    /**
     * @description 任务数量++
     * @private
     */
    async function handCurrentTaskAdd() {
        // 定时器延迟 1 毫秒执行
        setTimeout(() => {
            count++;
        }, 1);
    }

    /**
     * @description 任务数量--
     * @private
     */
    async function handCurrentTaskSub() {
        setTimeout(() => {
            count--;
        }, 1);
    }

    it('autoMicNumberCount test', async () => {
        const tasks = [];
        for (let index = 0; index < 10000; index++) {
            tasks.push(handCurrentTaskAdd());
        }
        for (let index = 0; index < 9000; index++) {
            tasks.push(handCurrentTaskSub());
        }
        await Promise.all(tasks);

        // 使用 Jest 的 advanceTimersByTime 方法来推进时间
        jest.advanceTimersByTime(100); // 推进足够的时间以确保所有回调都已执行

        // 确保所有 setTimeout 回调都已经执行
        expect(count).toBe(1000);
    });
});


http://www.kler.cn/a/286794.html

相关文章:

  • JavaScript笔记APIs篇01——DOM获取与属性操作
  • Lsky-Pro在线图片搭建教程(Docker部署方式)
  • 如何在 Pytest 中使用命令行界面和标记运行测试
  • 通过docker overlay2目录名查找容器名和容器ID
  • 基于STM32的智能门锁安防系统(开源)
  • 前端 window.print() 打印图片
  • 数据库性能测试2:内存数据库
  • 基于 Android Studio 实现的 记账本-MySQL版
  • [C#]国密SM2算法加解密字符串加密解密文件
  • 研究生深度学习入门的十天学习计划------第五天
  • 小琳python课堂:Python核心概念 类和对象
  • 折腾 Quickwit,Rust 编写的分布式搜索引擎 - 从不同的来源摄取数据
  • Django+Vue农产品销售系统的设计与实现
  • 理解大模型中的Cursor技术:优化长文本推理的前沿方案
  • 微服务集成 Seata
  • 【 html+css 绚丽Loading 】000030 灵文闪烁符
  • 【Selenium】UI自动化实践——输入验证码登录
  • Mysql基础练习题 1084.销售分析3 (力扣)
  • 数据结构--初步了解(抽象分级)
  • 【专题】2024年中国AI人工智能基础数据服务研究报告合集PDF分享(附原数据表)
  • 架构设计(13)安全架构设计理论
  • QT +ffmpeg-4.2.2-win64-shared 拉取 RTMP/http-flv 流播放
  • 模型 冯/诺依曼思维模型
  • 实习的一点回顾单元测试
  • 网络爬虫调研报告
  • Force Yc 第九引导公告页HTML源码