Node原子计数器

2024-08-31 06:12
文章标签 原子 node 计数器

本文主要是介绍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);});
});

这篇关于Node原子计数器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123112

相关文章

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件

Node Linux相关安装

下载经编译好的文件cd /optwget https://nodejs.org/dist/v10.15.3/node-v10.15.3-linux-x64.tar.gztar -xvf node-v10.15.3-linux-x64.tar.gzln -s /opt/node-v10.15.3-linux-x64/bin/npm /usr/local/bin/ln -s /opt/nod

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

SQL2005 性能监视器计数器错误解决方法

【系统环境】 windows 2003 +sql2005 【问题状况】 用户在不正当删除SQL2005后会造成SQL2005 性能监视器计数器错误,如下图 【解决办法】 1、在 “开始” --> “运行”中输入 regedit,开启注册表编辑器,定位到 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVer

在Debian 8上安装Node.js的方法

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 Node.js 是一个通用编程的 JavaScript 平台,允许用户快速构建网络应用程序。通过在前端和后端都使用 JavaScript,开发可以更加一致,并且可以在同一个系统中设计。 在本指南中,您将在 Debian 8 服务器上安装 Node.js。Debian 8 包含一个版本的

使用Node-API进行异步任务开发

一、Node-API异步任务机制概述         Node-API异步任务开发主要用于执行耗时操作的场景中使用,以避免阻塞主线程,确保应用程序的性能和响应效率。         1、应用场景: 文件操作:读取大型文件或执行复杂的文件操作时,可以使用异步工作项来避免阻塞主线程。网络请求:当需要进行网络请求并等待响应时,可以使用异步工作项来避免阻塞主线程,从而提高应用程序的响应性能。数据库操

Node.js学习记录(一)

目录 一、文件读取 readFile 二、写入文件 writeFile 三、动态路径 __dirname:表示当前文件所处的目录、path.join 四、获取路径文件名 path.basename 五、提取某文件中的css、JS、html 六、http 七、启动创建web服务器 服务器响应 八、将资源请求的 url 地址映射为文件的存放路径 九、模块 模块加载 模块作用域

ubuntu安装node的问题

初学node,按照书上的流程安装nodejs,当安装canvas的时候(npm install canvas)遇到问题 node-gyp rebuild 然后就卡住了,查了下,基本上就是各种依赖问题,几经折腾就在要放弃的时候,终于在网上发现node官方wike https://github.com/Automattic/node-canvas/wiki/Installation---Ubun

node快速复制文件或文件夹,排除部分文件(node_modules)

const fs = require('fs')const path = require('path')/*** @description: 获取完整的文件路径* @param {*} url 路径* @return {*} 返回完整的文件路径*/const getPath = (url) => {return path.join(__dirname, url)}/*** @descr

Node.js和vue3实现GitHub OAuth第三方登录

Node.js和vue3实现GitHub OAuth第三方登录 前言 第三方登入太常见了,微信,微博,QQ…总有一个你用过。 在开发中,我们希望用户可以通过GitHub账号登录我们的网站,这样用户就不需要注册账号,直接通过GitHub账号登录即可。 效果演示 注册配置 GitHub 应用 1.首先登录你的GitHub然后点击右上角的头像->点击进入Settings页面 2.在