导语:在我接触Nodejs的时候,听的最多的关键字就是:事件驱动、非阻塞I/O、高效、轻量,是单线程且支持高并发的脚本语言。可为什么单线程的nodejs可以支持高并发呢?很多人都不明白其原理,自己也在很长一段时间内被这些概念搞的是云里雾里。下面我们就来一步一步揭开其神秘的面纱。并且,通过底层C/C++源码的学习,来剖析Nodejs实现高并发的之一------事件循环的实现。

前言

从Node.js进入我们的视野时,我们所知道的它就由这些关键字组成 事件驱动、非阻塞I/O、高效、轻量,它在官网中也是这么描述自己的。

Node.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

于是在我们刚接触Nodejs时,会有所疑问:

1、为什么在浏览器中运行的Javascript 能与操作系统进行如此底层的交互?

2、nodejs 真的是单线程吗?

3、如果是单线程,他是如何处理高并发请求的?

4、nodejs 事件驱动是如何实现的?

等等。。。

看到这些问题,是否有点头大,别急,带着这些问题我们来慢慢看这篇文章。

架构一览

上面的问题,都挺底层的,所以我们从 Node.js 本身入手,先来看看 Node.js 的结构。

·Node.js 标准库,这部分是由 Javascript 编写的,即我们使用过程中直接能调用的 API。在源码中的 lib 目录下可以看到。

· Node bindings,这一层是 Javascript 与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据。实现在 http://node.cc

· 这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。

V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript 的关键,它为Javascript 提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。

Libuv:它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。

C-ares:提供了异步处理 DNS 相关的能力。

http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。

与操作系统交互

举个简单的例子,我们想要打开一个文件,并进行一些操作,可以写下面这样一段代码:

var fs = require('fs');

fs.open('./test.txt', "w", function(err, fd) {

//..do something

});

这段代码的调用过程大致可描述为:lib/fs.js → src/http://node_file.cc → uv_fs

lib/fs.js

async function open(path, flags, mode) {

mode = modeNum(mode, 0o666);

path = getPathFromURL(path);

validatePath(path);

validateUint32(mode, 'mode');

return new FileHandle(

await binding.openFileHandle(pathModule.toNamespacedPath(path),

stringToFlags(flags),

mode, kUsePromises));

}

static void Open(const FunctionCallbackInfo& args) {

Environment* env = Environment::GetCurrent(args);

const int argc = args.Length();

if (req_wrap_async != nullptr) { // open(path, flags, mode, req)

AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger,

uv_fs_open, *path, flags, mode);

} else { // open(path, flags, mode, undefined, ctx)

CHECK_EQ(argc, 5);

FSReqWrapSync req_wrap_sync;

FS_SYNC_TRACE_BEGIN(open);

int result = SyncCall(env, args[4], &req_wrap_sync, "open",

uv_fs_open, *path, flags, mode);

FS_SYNC_TRACE_END(open);

args.GetReturnValue().Set(result);

}

}

uv_fs

/* Open the destination file. */

dstfd = uv_fs_open(NULL,

&fs_req,

req->new_path,

dst_flags,

statsbuf.st_mode,

NULL);

uv_fs_req_cleanup(&fs_req);

Node.js 深入浅出上的一幅图:

具体来说,当我们调用 fs.open 时,Node.js 通过 process.binding 调用 C/C++ 层面的 Open 函数,然后通过它调用 Libuv 中的具体方法 uv_fs_open,最后执行的结果通过回调的方式传回,完成流程。

我们在 Javascript 中调用的方法,最终都会通过 process.binding 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。

单线程

在传统web 服务模型中,大多都使用多线程来解决并发的问题,因为I/O 是阻塞的,单线程就意味着用户要等待,显然这是不合理的,所以创建多个线程来响应用户的请求。

Node.js 对http 服务的模型:

Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。实践一个测试代码:

var http = require('http');

function sleep(time) {

var _exit = Date.now() + time * 1000;

while( Date.now() < _exit ) {}

return ;

}

var server = http.createServer(function(req, res){

sleep(10);

res.end('server sleep 10s');

});

server.listen(8080);

下面为代码块的堆栈图:

先将index.js的代码改成这样,然后打开浏览器,你会发现浏览器在10秒之后才做出反应,打出Hello Node.js。

JavaScript是解析性语言,代码按照编码顺序一行一行被压进stack里面执行,执行完成后移除然后继续压下一行代码块进去执行。上面代码块的堆栈图,当主线程接受了request后,程序被压进同步执行的sleep执行块(我们假设这里就是程序的业务处理),如果在这10s内有第二个request进来就会被压进stack里面等待10s执行完成后再进一步处理下一个请求,后面的请求都会被挂起等待前面的同步执行完成后再执行。

那么我们会疑问:为什么一个单线程的效率可以这么高,同时处理数万级的并发而不会造成阻塞呢?就是我们下面所说的--------事件驱动。

事件驱动/事件循环

Event Loop is a programming construct that waits for and dispatches events or messages in a program.

1、每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)。

2、主线程之外,还维护了一个"事件队列"(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。

3、主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。

4、主线程不断重复上面的第三步。

我们所看到的node.js单线程只是一个js主线程,本质上的异步操作还是由线程池完成的,node将所有的阻塞操作都交给了内部的线程池去实现,本身只负责不断的往返调度,并没有进行真正的I/O操作,从而实现异步非阻塞I/O,这便是node单线程和事件驱动的精髓之处了。

---------------------------------------------------------------------------

原文作者,腾讯工程师Howell。

你也想成为腾讯工程师?

也想年终奖人手一部 Iphone X?

那就快加入腾讯NEXT学位吧!

NEXT学位课程node.js课程第2期火热招生中!

为什么nodejs是单进程的_Nodejs探秘:深入理解单线程实现高并发原理相关推荐

  1. 理解Nodejs的单线程实现高并发原理

    组成和架构 Nodejs 的特点是事件驱动.非阻塞I/O.高效.轻量. 我们首先看下 Nodejs 的架构. 最上层的是 Nodejs标准库,由JavaScript实现的api库,位置在 lib 目录 ...

  2. 为什么nodejs是单进程的_nodejs真的是单线程吗?

    一.多线程与单线程 像java.python这个可以具有多线程的语言.多线程同步模式是这样的,将cpu分成几个线程,每个线程同步运行. 而node.js采用单线程异步非阻塞模式,也就是说每一个计算独占 ...

  3. 为什么nodejs是单进程的_Nodejs·进程

    之前对这部分的内容很感兴趣,没想到读起来有点晦涩,还是因为对服务器的知识不是很了解. 说道服务器一般人都会想到tomcat或者Jboss或者weblogic,现在流行起来的Node总让人不太放心,JS ...

  4. NodeJS深度探秘:通过爬虫用例展示callback hell的处理方法以及高并发编程的几个有效模式

    高并发和异步模式往往需要支持一种机制,那就是消息模式.当某个情况发送或是某种状态改变时,系统需要通知所有关注者,让他们及时进行处理,于是系统就会发送一个特定消息,所有监听该消息的对象在信号发出后,他们 ...

  5. nodejs与JAVA应对高并发的对比

    脱离带宽内存与计算量来讨论并发是没有意义的. 因为并发数受带宽及其它很多因素影响,不能单就node.js来说并发多高. 如果无限带宽,无限计算力,无限存--你可以认为node.js并发数也是无限的,但 ...

  6. node和php处理高并发,node.js“多线程”如何处理高并发任务?,nodejs java 高并发

    node.js"多线程"如何处理高并发任务?node . js"多线程"是如何处理高度并发的任务的?,下面的文章介绍了使用nodejs"多线程&quo ...

  7. nodeJS express mysql 高并发时连接数不够用问题 以及如何处理高并发

    首先 描述下问题,前段时间接到了通知,做nodejs高并发代码优化,于是开始整咯,首先用loadrunning模拟高并发,问题就来了,到高并发路由的时候,会出现数据库连接数不够用的情况.查询了代码,都 ...

  8. nodejs 当前文件路径_NodeJs的几种文件路径

    上次写删除文件夹的时候用到了fs模块,也集中用到了很多种路径,当时就想写一下,在Node中使用各种路径的问题,于是就简单写了一下,可以从 这里 获取demo源代码. 刚写Node的时候经常会遇到这种情 ...

  9. nodejs 获取文件路径_Nodejs读取文件时相对路径的正确写法(使用fs模块)

    在开发Nodejs中,我们往往最常用的模块就是fs核心模块(fs.readFile)来读取文件.代码如下: 但是运行之后,并没有按照想象中一样,读取test.html文件内容,这是一个bug,坑爹的玩 ...

最新文章

  1. php编程题试题和答案,比较基础的php面试题及答案-编程题部分
  2. 流行的9个Java框架介绍: 优点、缺点等等
  3. C++对象内存布局--⑤GCC编译器--单个虚拟继承
  4. P1031 均分纸牌(经典贪心)
  5. lazada选品,东南亚韩潮周边产品爆卖,单日销售额5万美金!
  6. c语言每个整数占9列,c语言 第五章 数据类型和表达式.ppt
  7. li或dd 浮动后增加图片时高度多出3-5px的问题
  8. hive中实现行转列_漫谈数据仓库之拉链表(原理、设计以及在Hive中的实现)
  9. systemctl命令
  10. Palindrome DP
  11. LeetCode 5377. 将二进制表示减到 1 的步骤数
  12. ORM Model查询页生成
  13. 自动生成xml报文_使用python如何给xml报文进行签名 signXML库
  14. ps切图教程 android,PS怎么切图 PS最新版本切图教程
  15. dede 表单必填_织梦给自定义表单增加必填功能,织梦表单必填设置
  16. 小程序分销的规则是怎样的?
  17. 关于S参数的一些理解
  18. 如何系统磁盘和raid卡的槽位对应起来
  19. Webpack构建vue项目-记录
  20. 隐藏身份证中间几位工具类

热门文章

  1. 云图说|华为HiLens云上管理平台 花样管理多种端侧设备
  2. 智能对联模型太难完成?华为云ModelArts助你实现!手把手教学
  3. 【华为云技术分享】GitHub联合开发
  4. 华为云DevCloud为开发者提供高效智能的可信开发环境
  5. HTML5唐诗三百首,《唐诗三百首》中王维5首五言绝句,代表了盛唐绝句的最高成就!...
  6. mysql count 1_高性能MySQL count(1)与count(*)的差别
  7. MATLAB绘图辅助操作
  8. Windows配置Gtkmm开发环境(with codeblocks)
  9. PyQt5学习笔记(二) 文本控件及使用
  10. R Studio更换外部包镜像的方法