欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > Node原子计数器

Node原子计数器

2024/10/24 11:15:29 来源:https://blog.csdn.net/qq_39517116/article/details/141730281  浏览:    关键词: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);});
});

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com