【问题标题】:How to sleep the thread in node.js without affecting other threads?如何让node.js中的线程休眠而不影响其他线程?
【发布时间】: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?

【问题讨论】:

标签: multithreading node.js asynchronous


【解决方案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. 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-10
      • 2011-05-14
      • 2017-02-28
      • 1970-01-01
      相关资源
      最近更新 更多