Node原子计数器

基础

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);
    });
});

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码定义了两个结构体,分别是 nf_conn 和 nf_conntrack。 nf_conn 结构体包含了以下成员: - ct_general:一个 nf_conntrack 结构体,用于跟踪连接的一般信息。 - lock:自旋锁,用于保护对 nf_conn 结构体的并发访问。 - cpu:一个 16 位的无符号整数,表示该连接所在的 CPU 编号。 - zone:一个 nf_conntrack_zone 结构体,在 CONFIG_NF_CONNTRACK_ZONES 宏开启时有效。 - tuplehash:一个包含了 IP_CT_DIR_MAX 个元素的 nf_conntrack_tuple_hash 数组,用于存储连接的原始和回复的元组信息。 - status:一个无符号长整型数,用于表示连接的状态。 - timeout:一个 32 位的无符号整数,表示连接被认为已经死亡的时间戳(以 jiffies32 表示)。 - ct_net:一个 possible_net_t 类型的变量,表示连接所属的网络命名空间。 - nat_bysource:一个 hlist_node 结构体,在 CONFIG_NF_NAT 宏开启时有效。 - __nfct_init_offset:一个空结构体,用于初始化其他成员。 - master:一个指向 nf_conn 结构体的指针,表示该连接的期望连接(expectation)。 - mark:一个 32 位的无符号整数,在 CONFIG_NF_CONNTRACK_MARK 宏开启时有效。 - secmark:一个 32 位的无符号整数,用于安全标记,在 CONFIG_NF_CONNTRACK_SECMARK 宏开启时有效。 - ext:一个指向 nf_ct_ext 结构体的指针,表示连接的扩展信息。 - proto:一个联合体,用于存储其他模块保留的数据。 nf_conntrack 结构体包含了一个名为 use 的 atomic_t 类型成员,用于记录 nf_conntrack 结构体的使用计数。atomic_t 是一个原子类型,用于实现原子操作的计数器。 需要注意的是,这段代码只是结构体的定义,没有展示结构体成员的具体实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值