共计 3242 个字符,预计需要花费 9 分钟才能阅读完成。
摩尔定律
摩尔定律是由英特尔联结创始人戈登·摩尔 (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.readFile
、http.request
。这些异步 I/O 底层是调用了新线程执行异步工作,再利用事件驱动的模式来获取执行后果。
服务端开发、工具开发可能都会须要应用到多线程开发。比方应用多线程解决简单的爬虫工作,用多线程来解决并发申请,应用多线程进行文件解决等等 …
在咱们应用多线程时,肯定要管制最大同时并发数。因为不管制最大并发数,可能会导致 文件描述符
耗尽引发的谬误,带宽有余引发的网络谬误、端口限度引发的谬误等等。
在 Node
中并没有用于管制最大并发数的 API
或者环境变量,所以接下来,咱们就用几行简略的代码来实现。
代码实现
咱们先假如上面的一个需要场景,我有一个爬虫,须要每天爬取 100 篇掘金的文章,如果一篇一篇爬取的话太慢,一次爬取 100 篇会因为网络连接数太多,导致很多申请间接失败。
那咱们能够来实现一下,每次申请 10 篇,分 10 次实现。这样不仅能够把效率晋升 10 倍,并且能够稳固运行。
上面来看看单个申请工作,代码实现如下:
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 个申请工作,代码实现如下:
// 申请工作列表 | |
const requestFnList = new Array(100) | |
.fill("6909002738705629198") | |
.map((id) => () => singleRequest(id)); |
接下来,咱们来实现并发申请的办法。这个办法反对同时执行多个异步工作,并且能够限度最大并发数。在工作池的一个工作执行实现后,新的异步工作会被推入继续执行,以保障工作池的高利用率。代码实现如下:
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.all
,Promise.all
能够同时执行多个异步工作。
在下面的代码中,创立了一个长度为 max
最大并发数长度的数组,数组里放了对应数量的异步工作。而后应用 Promise.all
同时执行这些异步工作,当单个异步工作执行实现时,会在工作池取出一个新的异步工作继续执行,实现了效率最大化。
接下来,咱们用上面这段代码进行执行测试(代码实现如下)
(async () => {const requestFnList = new Array(100) | |
.fill("6909002738705629198") | |
.map((id) => () => singleRequest(id)); | |
const reply = await concurrentRun(requestFnList, 10, "申请掘金文章"); | |
})(); |
最终执行后果如下图所示:
到这里,咱们的并发申请就实现啦!接下来咱们别离来测试一下不同并发的速度吧~ 首先是 1 个并发,也就是没有并发(如下图)
耗时 11.462 秒!当不应用并发时,工作耗时十分长,接下来咱们看看在其余并发数的状况下耗时(如下图)
从上图能够看出,随着咱们并发数的进步,工作执行速度越来越快!这就是高并发的劣势,能够在某些状况下晋升数倍乃至数十倍的效率!
咱们认真看看下面的耗时会发现,随着并发数的减少,耗时还是会有一个阈值,不能齐全呈倍数减少。这是因为 Node
实际上并没有为每一个工作开一个线程进行解决,而只是为异步 I/O
工作开启了新的线程。所以,Node
比拟适宜解决 I/O
密集型工作,并不适宜 CPU
(计算)密集型工作。
到这里,咱们的应用 Node“多线程”解决高并发工作就介绍完了。如果想要程序欠缺一点的话,还须要思考到工作超时工夫、容错机制,大家感兴趣的能够本人实现一下。
参考资料
- 《深入浅出 Nodejs》
- MBA 智库百科
- 百度百科
最初一件事
如果您曾经看到这里了,心愿您还是点个赞再走吧~
您的点赞是对作者的最大激励,也能够让更多人看到本篇文章!
如果感觉本文对您有帮忙,请帮忙在 github 上点亮 star
激励一下吧!