千家信息网

征服优雅、高效的Libuv库之初识篇

发表于:2024-11-22 作者:千家信息网编辑
千家信息网最后更新 2024年11月22日,这一系列文章主要分析nodejs中的核心库Libuv。我的参考书:朴灵的深入浅出nodejsJeffrey Richter的Windows核心编程Anthony Williams的C++并发编程实战暂
千家信息网最后更新 2024年11月22日征服优雅、高效的Libuv库之初识篇


这一系列文章主要分析nodejs中的核心库Libuv。


我的参考书:

朴灵的深入浅出nodejs

Jeffrey Richter的Windows核心编程

Anthony Williams的C++并发编程实战


暂定为四篇:

1) 征服之初识篇(背景基础以及重要的概念,图示,libuv的编译,例子)2) 征服之进展篇(内部用到的c语言技巧以及QUEUE的使用)3) 征服之高潮篇(线程池,iocp,同步,并发,线程间的通信等)4) 征服之和谐篇(Libuv的初始化,主循环,主线程和线程池之间的和谐交往)

通过征服Libuv系列文章,目的是让大家了解:

1) C语言之美(语法简单,功能强大,贴近硬件,适合系统级别编程,有很多技巧)2) 多线程与线程的同步技术3) 线程池技术4) windows IOCP技术5) 了解与ms PPL, intel TBB以及libdispatch(ios gcd)之间的异同点和优缺点。6) vs c++中多线程下的debug技巧

之所以称为了解,而不是掌握,是因为这些技术偏向于底层的系统编程,并不是一撮而就的,需要一定的时间和经历才能沉淀下来的。因此只能说是让大家了解。

为了简单期间,一些限制条件:

仅关注windows下的实现仅重点分析libuv的通用cpu密集型计算的框架,了解cpu密集计算框架,实际上异步io就比较容易理解了因为windows下,异步io通过IOCP(完成端口),会使代码实现非常简单,而效率非常高。

在写此篇blog时,突然感觉到,是不是应该将libuv库进行拆分,将核心部分代码抽离出来单独运行,这样的好处就是要分析的代码量要少很多,仅关注我们感兴趣的代码,有利于演示。

还有一个目的:

就是在简化精华版的基础上升级一下,看看是否能够实现任务的动态均衡(ms PPL库和intel TBB库都有动态均衡,Work Stealing的功能,libuv目前确定没有此功能,libdispatch目前没发现,源码正在阅读中,还没看到)。这个实现难度还是很大的,就当练练手吧,哈哈

我尝试一下吧,看看是否可行!

(本文写于2016年,经过这段时间研究,于2017-2-10放弃上面的拆分这个想法,实在是牵涉到的代码以及操作系统太多,还是果断放弃这个念想吧!)


1、背景:

前段时间,完成了一个微信项目,在后台选型过程中,花了将近一个月考察了java,php几个库,但最终却选择了nodejs,原因很简单:

1、java的配置实在是太麻烦了2、php实在不熟悉,特别扭3、nodejs使用js编程,我本人还是比较熟悉js的基础部分(不算es6.0 es7.0标准),并且npm真好用,实在是资源太丰富了,有时选择太多也是一种痛苦啊!4、nodejs基于异步io技术,效率非常高。而且分布式部署简单。

通过无数次的比较,最终选定了令我非常满意的组合,感觉最起码少写了80%的代码。

后台配套方案:

framework: strongloop/loopback(被IBM收购了,只能说强大无比)
朴灵的: wechat / wechat-api / wechat-oauth
supersheep: wechat-pay
数据库: mongodb用于不需要事务处理的表 mysql用于支付的事务处理
因为没经验,在支付时需要事务处理,所以调整为使用mysql。不过从另外一个角度说明loopback的强大,支持多数据源的统一api操作。

前台配套方案:

angularjs1.x
jquery

总体而言,该前后台配套系统的开发非常令人愉悦。前台不好统计,但是微信后台,至少少写了80%代码!!

2、演变:

目前基于异步回调的开发非常盛行,例如ios中的gcd,android中的基于接口回调方式。如果有过这些开发经验,你会觉得nodejs的异步事件开发模型还是蛮熟悉的。

随着逐渐熟悉nodejs,深深的喜欢上了nodejs,因此更想深入的了解一下nodejs是如何实现的。

通过了解nodejs的代码结构,结合深入浅出nodejs第三章的异步io所示流程图,逐步验证其正确性。特别是看到libuv中具有深厚c语言功底的老鸟所写的代码,忍不住想与大家分享c的代码之美!!

3、libuv是什么?

官方介绍:

  libuv is a multi-platform support library with a focus on asynchronous I/O.   It was primarily developed for use by Node.js

由上面的介绍,我们可以知道:

1) 跨平台:windows linux unix都支持2) 异步io:windows IOCP 、linux epoll、unix kqueue3) 主要目的是用于nodejs,实际还有很多库或程序使用了libuv   例如目前风头正劲的跨平台.NetCore库(就是曾经大名鼎鼎的微软的asp.net)也是使用libuv作为核心4) libuv不仅仅支持异步io操作,而且还具有一个强劲的线程池,用于支持多线程并行的cpu密集型操作。   这个才是我们重点要分析的模块

4、nodejs、google v8 javascript引擎和libuv之间的关系:

1) nodejs主要由google v8 javascript引擎和libuv组成2) v8引擎绑定libuv实现的api,因此,既能使用ecma js标准语言来执行js代码,又能通过js调用libuv相关接口。3) 由此可见,libuv本身是独立的c语言库,既可以直接使用c/c++来调用,也可以被绑定到c#(.NetCore)或者其他任何语言,例如java ,lua......   c/c++实现的库最大的好处就是能被各种其他编程语言所绑定和调用。   因为其他各种编程语言基本都是用c/c++来实现的,都留有接口与c/c++互调。

借用深入浅出nodejs(经作者同意)中的两张图来了解整个流程:


我们libuv源码分析以上图为指导,深入挖掘每个细节,验证其正确性,从而掌握整个libuv的精髓!

一定要明确的了解哪些事情是发生在主线程的,哪些事情是发生在线程池中的!!!

请将此图看上100遍,背出来,肯定有非常大帮助


nodejs就是数据通过v8引擎(主线程:数据输入)传递给Libuv进行处理(线程池:数据处理-根据数据类型不同,io数据由IOCP[windows]线程池处理,通用计算则由自己实现的线程池处理),libuv处理好后通知v8引擎我已经完成了,你来进行完成处理(主线程:完成回调,信息输出)。


其实从上面的叙述可以了解到以下几点:


1) v8 javascript引擎是单线程的,数据的输入,信息的输出(完成回调)都是在主线程中处理。这一点以后在源码分析中我们可以验证,通过vs强大的debug功能,我们可以很清晰的看到具体代码到底是运行在哪个线程中。


2) 但是数据处理模块(libuv)不是单线程的,它根据数据请求类型是否是io请求(socket,文件读写或管道等)还是work请求(非io请求)。不同的请求使用不同的处理策略。例如io请求,在windows下用IOCP,在linux下用epool。而work请求,windows和linux下都是使用统一的,自己实现的线程池。而我们的源码分析就是要证明上面的描述。


5、visual studio编译及测试Libuv库:

   1) 如何编译libuv库   2) 在测试libuv库的时候,如何解决各种链接错误   3) 重点是根据链接错误编号,通过msdn来定位问题、分析问题以及解决问题,掌握方法学是关键


编译:
1) 安装python2.,6或者2.7版本,并设置好环境变量。千万别安装3.0或以上版本。
目前很多跨平台库,都通过python脚本进行编译或引导,因此python是必装程序。、


2) 在cmd中:
cd到你libuv所在目录
并输入 git clone https://chromium.googlesource.com/external/gyp.git build/gyp
将google gyp系统克隆到libuv所在目录的build/gyp文件夹下面
由于GFW的关系,google网站无法访问,作为程序员,我想你应该有办法、


3) 运行libuv所在目录下的vcbuild.bat,生成visual studio解决方案。双击运行,然后F5编译调试,一切尽在掌握中!


如果你目前可以登陆google网站,则直接运行vcbuild.bat,如果没安装过gyp的话,自动会下载gyp构建系统。因此可以省略第二步

双击uv.sln工程,f5编译,一路顺风顺水,毫无难度!

2、如何测试运行:

因为Libuv在windows中编译后的结果是一个静态链接库,那么我们需要重新建个exe工程,并将libuv.lib链接入exe这个程序,步骤如下:

1) 新建一个win32/控制台/空项目,名称例如:LibuvTest


2) 在源文件中添加main.cpp文件,实现以下简单代码并运行,F5,调试运行

3) 如果运行时报无法启动程序,则将exe设置为启动项:


4) 使用uv_wort_t进行cpu密集计算的测试代码,F5编译调试


F5运行后出现链接错误

5) LNK2019链接错误,表示相关的Lib没有被引入,观察相关错误内容,可以确定Libuv.lib没有被导入。

1、查找到Libuv.lib被编译到的目录

2、在LibuvTest项目上右击鼠标,选择属性菜单,弹出libuvTest Property Pages,然后选择Linker/input/Additional Dependencies界面

3、相对路径方式,添加libuv.lib库

参考

6) 添加好libuv.lib后,继续F5调试,仍旧有大量链接错误:

7) 解决LNK4098错误,该错误是由于LIBCMTD库引起的,我们忽略该库,具体如下:

8) 解决LNK2019错误,该错误前面也提到过,是由于没有引入相应的lib导致的。

1、查看具体的链接错误描述,可以看到和socket相关的

2、msdn是法宝,在msdn中查找closesocket函数,看看closesocket属于哪个lib库的?

3、按照前面添加libuv.lib方式(),添加Ws2_32.lib。或者也可以参考该文档使用另外一种方式:#pragma comment(lib,"path")方式进行链接。

9) 周而复始,不断使用8)的方式,解决所有LNK2019链接错误



10) 最终需要的所有链接库

11) F5继续编译调试,正确运行输出结果

12) 至此为止,libuv的uv_work_t的Demo能够编译并且正确运行。我们也可以休息一下了!可能太基础了,但是也是体现了解决问题的方法,一并记录下来,供大家参考。


下一篇我们关注libuv中一个重要的数据结构:QUEUE的实现和使用。

该结构在多个线程之间传送数据,因此必须要深入了解其原理和实现。



0