如何让node.js中的线程休眠而不影响其他线程?

Posted

技术标签:

【中文标题】如何让node.js中的线程休眠而不影响其他线程?【英文标题】:How to sleep the thread in node.js without affecting other threads? 【发布时间】:2012-11-07 01:18:35 【问题描述】:

根据Understanding the node.js event loop,node.js 支持单线程模型。这意味着如果我向 node.js 服务器发出多个请求,它不会为每个请求生成一个新线程,而是会一个一个地执行每个请求。这意味着如果我在我的 node.js 代码中对第一个请求执行以下操作,同时一个新请求进入节点,第二个请求必须等到第一个请求完成,包括 5 秒的睡眠时间。对吧?

var sleep = require('sleep');
    sleep.sleep(5)//sleep for 5 seconds

有没有办法让 node.js 为每个请求生成一个新线程,这样第二个请求就不必等待第一个请求完成,或者我可以只在特定线程上调用 sleep?

【问题讨论】:

你不需要那样睡觉。你应该看看 setTimeout -> nodejs.org/api/globals.html#globals_settimeout_cb_ms 【参考方案1】:

如果您指的是 npm 模块 sleep,它会在自述文件中注明 sleep 将阻止执行。所以你是对的——这不是你想要的。相反,您想使用非阻塞的setTimeout。这是一个例子:

setTimeout(function() 
  console.log('hello world!');
, 5000);

对于希望使用 es7 async/await 执行此操作的任何人,此示例应该会有所帮助:

const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));

const example = async () => 
  console.log('About to snooze without halting the event loop...');
  await snooze(1000);
  console.log('done!');
;

example();

【讨论】:

so node.js 将一次执行一个请求,这意味着它遵循单线程模型。对吗? 是的,它使用单线程执行,因此它一次只能计算一个请求,但可能有许多请求的未决事件。当您调用setTimeout 时,它会将事件存储在队列中,然后可以开始执行下一个请求。五秒后,事件将从队列中移除并执行回调(本例中包含console.log的函数)。 查看this screencast 了解有关 Node.js 如何运行多个请求而不让它们相互“等待”的更多详细信息。 它不会阻塞线程。此 setTimeout 之后的代码将继续运行,然后在 5 秒后控制台打印“hello world”。 @DavidWeldon,在将 setTimeout 从队列中移除后,Node 如何决定在某个时间点触发它?换句话说,如果函数不在执行模式/上下文中,是什么触发函数在五秒后运行?【参考方案2】:

如果您有一个循环,每个循环都有一个异步请求,并且您希望每个请求之间有一定的时间,您可以使用以下代码:

   var startTimeout = function(timeout, i)
        setTimeout(function() 
            myAsyncFunc(i).then(function(data)
                console.log(data);
            )
        , timeout);
   

   var myFunc = function()
        timeout = 0;
        i = 0;
        while(i < 10)
            // By calling a function, the i-value is going to be 1.. 10 and not always 10
            startTimeout(timeout, i);
            // Increase timeout by 1 sec after each call
            timeout += 1000;
            i++;
        
    

此示例在发送下一个请求之前在每个请求后等待 1 秒。

【讨论】:

-1。这不会按照您的预期进行。循环不会异步运行。它将立即触发所有请求。在考虑使用异步循环时要非常小心——这种组合很棘手。 当然循环会被一次性调用。但是超时会增加每个循环。所以1秒... 2秒... 3秒。这就是为什么“myAsyncFunc”每秒都会被调用而不是一次调用的原因。当我测试它时,它按预期工作。你能告诉我你认为它有什么问题吗? 代码确实有效,顺便说一句,有点棘手。 startTimeout 函数立即被调用 10 次,但每次调用的超时时间都大一秒,因此对 myAsyncFunc 的每次调用都会在一秒后调用。 但这违背了循环的全部目的。您应该能够决定循环运行多少次。您在上面所做的将恰好执行 10 次循环——无论您是否需要 10 次迭代。您无法在上一次迭代结束时决定是否要进行另一次迭代——因为您不知道上一次迭代何时结束。因此,您必须提前决定将发生多少次迭代。然后,迭代中断逻辑将泄漏到主题回调中 - 在您的情况下为 myAsyncFunc 第二个缺陷是您假设您的回调执行时间不会超过 1 秒。如果您的下一次迭代取决于您上一次迭代的结果,并且不能保证该结果在 1 秒内可用,该怎么办。然后你的下一次迭代将在没有所需结果的情况下开始——因为它会在上一次迭代后正好 1 秒开始,而不需要注意从上一次迭代调用的回调是否实际结束。【参考方案3】:

请考虑 deasync 模块,我个人不喜欢 Promise 方式使所有函数异步,以及关键字 async/await 任何那里。而且我认为官方node.js应该考虑暴露事件循环API,这样可以简单地解决回调地狱。 Node.js 是一个框架而不是一种语言。

var node = require("deasync");
node.loop = node.runLoopOnce;

var done = 0;
// async call here
db.query("select * from ticket", (error, results, fields)=>
    done = 1;
);

while (!done)
    node.loop();

// Now, here you go

【讨论】:

【参考方案4】:

当使用第三方库(例如 Cloud firestore)提供的异步函数或可观察对象时,我发现下面显示的 waitFor 方法(TypeScript,但你明白了......)对你有帮助需要等待某个过程完成,但您不想在回调中嵌入回调,也不想冒无限循环的风险。

这种方法有点类似于while (!condition) 睡眠循环,但是 异步产生并定期对完成条件执行测试,直到为真或超时。

export const sleep = (ms: number) => 
    return new Promise(resolve => setTimeout(resolve, ms))

/**
 * Wait until the condition tested in a function returns true, or until 
 * a timeout is exceeded.
 * @param interval The frenequency with which the boolean function contained in condition is called.
 * @param timeout  The maximum time to allow for booleanFunction to return true
 * @param booleanFunction:  A completion function to evaluate after each interval. waitFor will return true as soon as the completion function returns true.   
 */
export const waitFor = async function (interval: number, timeout: number,
    booleanFunction: Function): Promise<boolean> 
    let elapsed = 1;
    if (booleanFunction()) return true;
    while (elapsed < timeout) 
        elapsed += interval;
        await sleep(interval);
        if (booleanFunction()) 
            return true;
        
    
    return false;

假设您的后端有一个长时间运行的进程,您希望在执行其他任务之前完成该进程。例如,如果您有一个汇总帐户列表的函数,但您想在计算之前从后端刷新帐户,您可以执行以下操作:

async recalcAccountTotals() : number 
     this.accountService.refresh();   //start the async process.
     if (this.accounts.dirty) 
           let updateResult = await waitFor(100,2000,()=> return !(this.accounts.dirty))
     
 if(!updateResult)  
      console.error("Account refresh timed out, recalc aborted");
      return NaN;
    
 return ... //calculate the account total. 

【讨论】:

以上是关于如何让node.js中的线程休眠而不影响其他线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用信号调用同一进程中的另一个线程在轮询函数上休眠的进程而不杀死它?

C# 中的 Thread.Sleep():它是让实例化对象的线程休眠,还是让我从中调用方法的线程休眠?

在 Python 中生成音调而不冻结线程?

为啥 Spring 批处理多线程步骤会破坏 ItemProcessor 中的其他存储库休眠查询?

JAVA多线程中,如何让一个线程去等待N个线程执行完成后,再执行。

让一个线程进入休眠状态,直到另一个线程解决了某个条件