Node.js知识 — 如何实现线程睡眠?

开发 前端
使用 JavaScript/Node.js 的开发者如果遇到需要实现延迟的任务,可能会有疑问🤔️ 为什么这里没有类似 Java 中 Thread.sleep() 这样的方式来实现线程睡眠,本文讲解如何在 Node.js 中实现一个 sleep() 函数。

[[386225]]

本文转载自微信公众号「Nodejs技术栈」,作者五月君。转载本文请联系Nodejs技术栈公众号。

Node.js 小知识 记录一些工作中或 “Nodejs技术栈” 交流群中大家遇到的一些问题,有时一个小小的问题背后也能延伸出很多新的知识点,解决问题和总结的过程本身也是一个成长的过程,在这里与大家共同分享成长。

使用 JavaScript/Node.js 的开发者如果遇到需要实现延迟的任务,可能会有疑问🤔️ 为什么这里没有类似 Java 中 Thread.sleep() 这样的方式来实现线程睡眠,本文讲解如何在 Node.js 中实现一个 sleep() 函数。

一:糟糕的 “循环空转”

下面这段代码是糟糕的,Node.js 是以单进程、单线程的方式启动,所有的业务代码都工作在主线程,这样会造成 CPU 持续占用,主线程阻塞对 CPU 资源也是一种浪费,与真正的线程睡眠相差甚远。

  1. const start = new Date(); 
  2. while (new Date() - start < 2000) {} 

 

运行之后如上图所示,CPU 暴涨,同时也会破坏事件循环调度,导致其它任务无法执行。

二:定时器 + Promise 实现 sleep

通过定时器延迟执行函数 setTimeout + Promise 的链式依赖实现,本质是创建一个新的 Promise 对象,待定时器延迟时间到了执行 resolve 函数这时 then 才会执行,这里 Node.js 执行线程是没有进行睡眠的,事件循环和 V8 等都是正常运行的。但这也是目前通用的一种解决方案,因为你不能让主线程阻塞,否则程序就无法继续工作了。

  1. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); 

在 Node.js 中还可以利用 util 模块提供的 promisify 方法实现,一种快捷方式。

  1. const { promisify } = require('util'); 
  2. const sleep = promisify(setTimeout); 

因为是基于定时器与 Promise 所以也自然是异步的方式了,使用时也要注意,如下所示:

  1. // async await 的方式 
  2. async function test() { 
  3.   console.log(1); 
  4.   await sleep(3000); 
  5.   console.log(2); 
  6.  
  7. // Promise 的链式调用方式 
  8. async function test() { 
  9.   console.log(1); 
  10.   sleep(3000).then(() => { 
  11.     console.log(2); 
  12.   }); 

三:零 CPU 开销真正的事件循环阻止 sleep 实现

ECMA262 草案提供了 Atomics.wait API 来实现线程睡眠,它会真正的阻塞事件循环,阻塞线程直到超时。

该方法 Atomics.wait(Int32Array, index, value[, timeout]) 会验证给定的 Int32Array 数组位置中是否仍包含其值,在休眠状态下会等待唤醒或直到超时,返回一个字符串表示超时还是被唤醒。

同样的因为我们的业务是工作在主线程,避免在主线程中使用,在 Node.js 的工作线程中可以根据实际需要使用。

  1. /** 
  2.  * 真正的阻塞事件循环,阻塞线程直到超时,不要在主线程上使用  
  3.  * @param {Number} ms delay 
  4.  * @returns {String} ok|not-equal|timed-out 
  5.  */ 
  6. function sleep(ms) { 
  7.   const valid = ms > 0 && ms < Infinity; 
  8.   if (valid === false) { 
  9.     if (typeof ms !== 'number' && typeof ms !== 'bigint') { 
  10.       throw TypeError('ms must be a number'); 
  11.     } 
  12.     throw RangeError('ms must be a number that is greater than 0 but less than Infinity'); 
  13.   } 
  14.  
  15.   return Atomics.wait(int32, 0, 0, Number(ms)) 
  16.  
  17. sleep(3000) 

由于本节我们仅是在讲解 sleep 的实现,所以关于 Atomics.wait 方法睡眠之后如何被其它线程唤醒也不再此处讲了,之后我会写一讲 Node.js 中的工作线程相关文章,到时会再次介绍。

四:基于 N-API 扩展使用 C 语言实现 sleep

通过 Addon 的方式使用 N-API 编写 C/C++ 插件,借助其提供的系统 sleep() 函数实现。

  1. // sleep.c 
  2. #include <assert.h> 
  3. #include <unistd.h> 
  4. #include <node_api.h> 
  5.  
  6. napi_value sleepFn(napi_env env, napi_callback_info info) { 
  7.   napi_status status; 
  8.   size_t argc = 1; 
  9.   napi_value argv[1]; 
  10.  
  11.   status = napi_get_cb_info(env, info, &argc, argv, NULLNULL); 
  12.   assert(status == napi_ok); 
  13.   if (argc < 1) { 
  14.     napi_throw_type_error(env, NULL"ms is required"); 
  15.     return NULL
  16.   } 
  17.  
  18.   napi_valuetype valueType; 
  19.   napi_typeof(env, argv[0], &valueType); 
  20.   if (valueType != napi_number) { 
  21.     napi_throw_type_error(env, NULL"ms must be a number"); 
  22.     return NULL
  23.   } 
  24.  
  25.   int64_t s; 
  26.   napi_get_value_int64(env, argv[0], &s); 
  27.   sleep(s); 
  28.   return NULL
  29.  
  30. napi_value init(napi_env env, napi_value exports) { 
  31.   napi_status status; 
  32.   napi_property_descriptor descriptor = { 
  33.     "sleep"
  34.     0, 
  35.     sleepFn, 
  36.     0, 
  37.     0, 
  38.     0, 
  39.     napi_default, 
  40.     0 
  41.   }; 
  42.   status = napi_define_properties(env, exports, 1, &descriptor); 
  43.   assert(status == napi_ok); 
  44.   return exports; 
  45.  
  46. NAPI_MODULE(sleep, init); 

经过一系列编译之后,引入 .node 文件直接使用。

  1. // app.js 
  2. const { sleep } = require('./build/Release/sleep.node'); 
  3. sleep(3); 

五:easy-sleep 模块

这是笔者写的一个小模块 https://github.com/qufei1993/easy-sleep,其实也是对以上几种方法的整合,包含了 C 插件的编写,使用如下:

  1. // Install 
  2. npm install easy-sleep -S 
  3.  
  4. // Async sleep 
  5. const { sleep } = require('easy-sleep'); 
  6. await sleep(3000); 
  7.  
  8. // Thread sleep 
  9. const { Thread } = require('easy-sleep'); 
  10. Thread.sleep(); 

 

总结

由于 JavaScript 是单线程的语言,通常我们都是工作在主线程,如果真的让线程睡眠了,事件循环也会被阻塞,后续的程序就无法正常工作了,大多数情况,我们也是简单的对 setTimeout 函数做一些封装实现延迟功能。在浏览器/Node.js 的工作线程下可以根据实际需要决定是否需要工作线程睡眠。

 

责任编辑:武晓燕 来源: Nodejs技术栈
相关推荐

2011-09-09 14:23:13

Node.js

2019-03-29 16:40:02

Node.js多线程前端

2021-05-21 09:36:42

开发技能代码

2021-01-04 08:09:58

Node.js磁盘接口

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2021-05-11 07:45:00

HTTPNode.jsCookie

2022-06-23 06:34:56

Node.js子线程

2021-07-16 04:56:03

NodejsAddon

2011-09-02 14:47:48

Node

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2021-04-20 12:39:52

Node.js多线程多进程

2022-01-29 22:27:31

内核子线程应用

2022-09-04 15:54:10

Node.jsAPI技巧

2021-05-27 09:00:00

Node.js开发线程

2021-08-24 05:00:21

Nodejs线程

2021-08-04 23:30:28

Node.js开发线程
点赞
收藏

51CTO技术栈公众号