Nodejs 全局变量的读写并发

Nodejs 全局变量的读写并发

var a = {};为全局变量,write(a)基于事件,可对a进行修改。 想问的是,当一个子线程收到事件,正在对a进行修改的同时,主线程调用get(a)尝试读取a的值。会怎样?

5 回复

Node.js 全局变量的读写并发

在Node.js中,全局变量可以在不同的执行上下文中共享。然而,由于JavaScript本身是单线程的,因此通常不会遇到典型的多线程并发问题。但是,如果你使用某些库或者自定义逻辑来模拟多线程行为(例如通过worker_threads模块),那么就需要考虑并发访问的问题。

示例场景

假设我们有一个全局变量 a,并且我们希望在一个子线程中修改它,并且在主线程中读取它的值。以下是一个简单的示例:

// 主线程代码
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
    global.a = {};

    const worker = new Worker(__filename);
    
    // 子线程修改全局变量
    worker.on('message', (msg) => {
        console.log(`主线程接收到消息: ${JSON.stringify(msg)}`);
    });

    // 主线程读取全局变量
    setTimeout(() => {
        console.log(`主线程读取到的a:`, global.a);
    }, 1000);
} else {
    // 子线程代码
    global.a.b = 'hello';
    parentPort.postMessage(global.a);
}

在这个例子中,主线程创建了一个新的工作线程(Worker),并在主线程中设置了一个全局变量 a。子线程修改了这个全局变量,然后将结果发送回主线程。主线程在一段时间后读取这个全局变量。

并发问题

如果我们在子线程修改 a 的同时,主线程立即读取 a 的值,可能会遇到并发问题。例如:

// 主线程代码
const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
    global.a = {};

    const worker = new Worker(__filename);

    // 子线程修改全局变量
    worker.on('message', (msg) => {
        console.log(`主线程接收到消息: ${JSON.stringify(msg)}`);
    });

    // 主线程立即读取全局变量
    console.log(`主线程立即读取到的a:`, global.a);
} else {
    // 子线程代码
    global.a.b = 'hello';
    parentPort.postMessage(global.a);
}

在这种情况下,主线程可能在子线程完成修改之前读取到不完整的或未初始化的值。为了避免这种情况,可以使用锁机制或者确保在读取之前等待写操作完成。

锁机制示例

为了确保并发安全,我们可以使用一个简单的锁机制来同步读写操作:

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
    global.a = {};
    global.lock = false;

    const worker = new Worker(__filename);

    worker.on('message', (msg) => {
        console.log(`主线程接收到消息: ${JSON.stringify(msg)}`);
    });

    // 使用锁机制确保读写操作的顺序
    worker.on('online', () => {
        global.lock = true;
        setTimeout(() => {
            global.lock = false;
            console.log(`主线程读取到的a:`, global.a);
        }, 1000);
    });
} else {
    global.a.b = 'hello';
    global.lock = false;
    parentPort.postMessage(global.a);
}

在这个示例中,我们使用了一个全局变量 lock 来控制读写操作的顺序。主线程在读取全局变量之前等待 lock 变为 false,从而确保读取时全局变量已经被正确修改。

通过这种方式,我们可以避免并发读写导致的数据不一致问题。


Node中执行js程序时是单线程的,不可能出现这个问题

我一直困惑,在event loop里面,某个事件被激活要去执行的时候,是node主进程去执行,还是底层C代码会fork一个子线程去执行? 如果是后者,怎么保证不冲突呢。

如果如你所说不可能出现,我想设计一个用例去证明。这个用例的设计能否不吝赐教?

是的,nodejs是单线程异步回调,里面只有进程的概念了

在Node.js中,全局变量(如var a = {})的读写并发问题可以通过多种方式解决。Node.js本身是单线程的,但在处理异步操作时可能会遇到并发问题。为了确保全局变量的安全访问和修改,可以使用锁机制或Promises来同步对全局变量的操作。

示例代码

假设我们有一个全局对象 a,并且有两个函数 write(a)get(a) 分别用于写入和读取该对象。我们可以使用 async/await 来确保这些操作是顺序执行的。

let a = {};

const lock = false;

// 模拟写入操作
function write(newValue) {
    return new Promise((resolve, reject) => {
        if (lock) {
            setTimeout(() => write(newValue), 100); // 如果被锁定,则等待100ms后重试
            return;
        }
        lock = true; // 加锁
        try {
            a = newValue;
            console.log('Write:', a);
            lock = false; // 解锁
            resolve();
        } catch (error) {
            lock = false; // 解锁
            reject(error);
        }
    });
}

// 模拟读取操作
function get() {
    return new Promise(resolve => {
        if (lock) {
            setTimeout(() => get(), 100); // 如果被锁定,则等待100ms后重试
            return;
        }
        lock = true; // 加锁
        try {
            console.log('Read:', a);
            lock = false; // 解锁
            resolve(a);
        } catch (error) {
            lock = false; // 解锁
            reject(error);
        }
    });
}

// 使用示例
(async () => {
    await write({ key: 'value' });
    const result = await get();
    console.log('Final Value:', result);
})();

解释

  • 加锁与解锁:我们在写入和读取操作之间添加了锁机制,以确保在同一时间只有一个操作在执行。
  • 异步操作:通过返回 Promise,我们可以使用 async/await 来确保操作按顺序执行。
  • 重试机制:如果当前操作被锁定,则将操作延迟100毫秒后重试,这有助于避免竞态条件。

这种方法虽然简单,但适用于简单的场景。对于更复杂的应用,建议使用专门的锁库或消息队列来管理并发操作。

回到顶部