node.js“多线程”如何处理高并发任务?


本文摘自PHP中文网,作者青灯夜游,侵删。

下面本篇文章给大家介绍一下使用 nodejs “多线程”处理高并发任务的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

相关推荐:《nodejs视频教程》

摩尔定律

摩尔定律是由英特尔联合创始人戈登?摩尔(Gordon Moore)在 1965 年提出的,即集成电路上可容纳的元器件的数量每隔 18 至 24 个月就会增加一倍,性能也将提升一倍。也就是说,处理器(CPU)的性能每隔大约两年就会翻一倍。

距离摩尔定律被提出到现在,已经过去了 50 多年。如今,随着芯片组件的规模越来越接近单个原子的规模,要跟上摩尔定律的步伐变得越来越困难。

在 2019 年,英伟达 CEO 黄仁勋在 ECS 展会上说:“摩尔定律过去是每 5 年增长 10 倍,每 10 年增长 100 倍。而如今,摩尔定律每年只能增长几个百分点,每 10 年可能只有 2 倍。因此,摩尔定律结束了。”

单个处理器(CPU)的性能越来越接近瓶颈,想要突破这个瓶颈,则需要充分利用 多线程技术,让单个或多个 CPU 可以同时执行多个线程,更快的完成计算机任务。

Node 的多线程

我们都知道,Javascript 是单线程语言,Nodejs 利用 Javascript 的特性,使用事件驱动模型,实现了异步 I/O,而异步 I/O 的背后就是多线程调度。

Node 异步 I/O 的实现可以参考朴灵的 《深入浅出 Node.js》

Go 语言中,可以通过创建 Goroutine 来显式调用一条新线程,并且通过环境变量 GOMAXPROCS 来控制最大并发数。

Node 中,没有 API 可以显式创建新线程的 ,Node 实现了一些异步 I/O 的 API,例如 fs.readFilehttp.request。这些异步 I/O 底层是调用了新线程执行异步任务,再利用事件驱动的模式来获取执行结果。

服务端开发、工具开发可能都会需要使用到多线程开发。比如使用多线程处理复杂的爬虫任务,用多线程来处理并发请求,使用多线程进行文件处理等等...

在我们使用多线程时,一定要控制最大同时并发数。因为不控制最大并发数,可能会导致 文件描述符 耗尽引发的错误,带宽不足引发的网络错误、端口限制引发的错误等等。

Node 中并没有用于控制最大并发数的 API 或者环境变量,所以接下来,我们就用几行简单的代码来实现。

代码实现

我们先假设下面的一个需求场景,我有一个爬虫,需要每天爬取 100 篇掘金的文章,如果一篇一篇爬取的话太慢,一次爬取 100 篇会因为网络连接数太多,导致很多请求直接失败。

那我们可以来实现一下,每次请求 10 篇,分 10 次完成。这样不仅可以把效率提升 10 倍,并且可以稳定运行。

下面来看看单个请求任务,代码实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

const axios = require("axios");

 

async function singleRequest(article_id) {

  // 这里我们直接使用 axios 库进行请求

  const reply = await axios.post(

    "https://api.juejin.cn/content_api/v1/article/detail",

    {

      article_id,

    }

  );

 

  return reply.data;

}

为了方便演示,这里我们 100 次请求的都是同一个地址,我们来创建 100 个请求任务,代码实现如下:

1

2

3

4

// 请求任务列表

const requestFnList = new Array(100)

  .fill("6909002738705629198")

  .map((id) => () => singleRequest(id));

接下来,我们来实现并发请求的方法。这个方法支持同时执行多个异步任务,并且可以限制最大并发数。在任务池的一个任务执行完成后,新的异步任务会被推入继续执行,以保证任务池的高利用率。代码实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

const chalk = require("chalk");

const { log } = require("console");

 

/**

 * 执行多个异步任务

 * @param {*} fnList 任务列表

 * @param {*} max 最大并发数限制

 * @param {*} taskName 任务名称

 */

async function concurrentRun(fnList = [], max = 5, taskName = "未命名") {

  if (!fnList.length) return;

 

  log(chalk.blue(`开始执行多个异步任务,最大并发数: ${max}`));

  const replyList = []; // 收集任务执行结果

  const count = fnList.length; // 总任务数量

  const startTime = new Date().getTime(); // 记录任务执行开始时间

 

  let current = 0;

  // 任务执行程序

  const schedule = async (index) => {

    return new Promise(async (resolve) => {

      const fn = fnList[index];

      if (!fn) return resolve();

 

      // 执行当前异步任务

      const reply = await fn();

      replyList[index] = reply;

      log(`${taskName} 事务进度 ${((++current / count) * 100).toFixed(2)}% `);

 

      // 执行完当前任务后,继续执行任务池的剩余任务

      await schedule(index + max);

      resolve();

    });

  };

 

  // 任务池执行程序

  const scheduleList = new Array(max)

    .fill(0)

    .map((_, index) => schedule(index));

  // 使用 Promise.all 批量执行

  const r = await Promise.all(scheduleList);

 

  const cost = (new Date().getTime() - startTime) / 1000;

  log(chalk.green(`执行完成,最大并发数: ${max},耗时:${cost}s`));

  return replyList;

}

从上面的代码可以看出,使用 Node 进行并发请求的关键就是 Promise.allPromise.all 可以同时执行多个异步任务。

阅读剩余部分

相关阅读 >>

关于前端面试(二)

popper.js怎么下载

javascript异步编程的4种方法

javascript中什么是字符串

理解javascript之async/await的新语法

js中什么是原型

bootstrap请求javascript失败是怎么回事

javascript对象封装的方法有哪些

javascript之ssm+vue前后端分离框架整合实现

怎么使用javascript将对象转为字符串

更多相关阅读请进入《javascript》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...