精华内容
下载资源
问答
  • 作者 | Ryan Dahl编译 | AI100tinyclouds.org/residency/去年,在我研究TensorFlow出了一番成果后,我开始申请Google Brain的首届见习项目(Google Brain Residency Program),最后居然成功了。...

    作者 | Ryan Dahl

    编译 | AI100 

    tinyclouds.org/residency/


    去年,在我研究TensorFlow出了一番成果后,我开始申请Google Brain的首届见习项目(Google Brain Residency Program),最后居然成功了。受邀参加该项目的共有24人,每个人都有着不同的机器学习背景。

    我们24个人,需要在Google位于山景城的深度学习研究实验室工作一年,每天跟Google的科学家和工程师们一起,共同来做TensorFlow的前沿研究。想想就把我给兴奋坏了。

    如今,这个为期一年的项目已经结束了,确实收获满满。我也希望把学到的一些东西,结合我的心得,总结一下,分享出来,希望能为在机器学习的道路上耕耘的伙伴儿提供一些帮助。

    先说说我起初拟定的目标吧。我的目标是修正老电影或电视剧的画面

    想象一下,画面粗糙的90年代电视剧,或是60年代黑白电影,要是能被修正为色彩华丽的4K画面,观看体验会有多棒!

    而这事看上去完全可行:我们很轻松就能把4K视频转换成满是颗粒感的、低分辨率的、甚至是只有黑白两色的视频,只要训练出某个监督模型来反转这个过程就可以了。而且,可用的训练数据无穷无尽。咳咳,这个想法简直太强悍了!

    带着这样的目标,我再一次(上一次是为Node.js项目)从纽约布鲁克林搬到旧金山湾区,以更好地实现这项深度学习技术。几天后,我的日常生活就变成了跟Google的深度学习专家进行讨论、在Google庞大的软件库内浏览代码,blablabla...

    接下来我要开始大谈这些日子以来,我所做的很多技术细节。当然,如果不想看,可以直接跳到总结部分。

    超分辨率的像素递归

    众所周知,FBI在《犯罪现场调查》(CSI)中所用的缩放技术是不可能实现的。没人能任意放大照片。然而,在你放大照片图像时把相关像素所构成的合理图形呈现出来,这还是有可能做到的。能够平滑地提升图像分辨率,将是我实现目标的第一步。

    该问题在本文中用 超分辨率 一词来描述,很久以前人们就在尝试解决它了。

    据此,我们认识到简单使用ConvNet无法彻底解决该问题:它只是把你输入的低分辨率图像的像素间距(L2)最小化来输出高分辨率图像。这类损失函数所学到的,是输出所有可能结果的平均值——所输出的图像看上去就比较模糊了。我们想要的模型是这样的:对于给定的低分辨率图像,它能从所有可能的强化结果中选出那张特定的、效果最好的高分辨率图像。如果是“强化”一张关于树的模糊照片,我们会希望它能给出枝、叶在位置上的一些细节,即便它们的位置并非是枝、叶在树上的实际位置。

    某种条件型的GAN(生成式对抗网络)看上去很有希望,但构建起来较难,经过几次失败的尝试后,我们换成了另一种新型的生产式模型:PixelCNN,它也比较有戏。(等我们启动之后,用GAN解决来超分辨率问题的SRGAN就发布了,它生成的结果非常好。)

    PixelCNN是一种奇怪的反直觉模型。它将图像生成问题重写成每次选择一个像素序列。像LSTM(长短时记忆网络)这样的门控制递归网络在序列生成方面是非常成功的,它通常会用在单词或字符上。PixelCNN巧妙地构建出一个卷积神经网络(CNN),它能基于先前的像素的概率分布来精确生成像素。这是RNN和CNN的混合功能。

    示意图由 van den Oord 等人所绘

    意外的是,PixelCNN所生成的图像看起来非常自然。不像艰难平衡两种目标的对抗网络,该模型的目标只有一个,因而面对超参数的变化,它有更好的稳健性。也就是说,它更易于优化。

    解决超分辨率问题的首次尝试,我野心过大,选用了ImageNet来训练PixelCNN。(跟CIFAR-10、CelebA或LSUN相比,ImageNet是个较难的数据集,很多生成式模型研究都在用它。)但很显然,按像素来序列生成图像的过程极其缓慢。输出图像的尺寸大于64x64时,耗时将超过数小时!然而,在我把图像的尺寸限制到小尺寸,并使用脸部或卧室类的小型数据集后,得出的结果就开始令人激动了。

    图1:用名人脸部图像数据集训练出来的超分辨率像素递归模型所生成的高分辨率图像。左侧为测试数据集所用的8x8低分辨率输入图像。中间为PixelCNN模型所输出的32x32高分辨率图像,右侧是原始的32x32分辨率图像。我们的模型优先整合脸部特征,而后去合成较为逼真的头发与皮肤方面的细节。

    由于在Google可以获取到无穷的计算资源,如何扩大训练的规模便成为该项目的另一个目标——因为即便采用这些小型的数据集,在单个CPU上完成训练也要花上数周的时间。

    异步随机梯度下降算法(Asynchronous SGD)是最理想的分布式训练方法。使用这种方法,你用N台机器独立来训练同以个模型,但每个时间步长都要共享一次权重参数。权重参数被托管在一台单独的“参数服务器”上,该服务器在每个时间步长内都进行远程过程调用(RPC),以获得最新数值并发送梯度更新。如果数据管道足够好,你就可以线性增加模型每秒内的训练步数,方法是增加机器——因为机器之间互不依赖。然而,当机器增加时,由于老机器更新了权重,新机器的权重会逐步过期或“落伍”。在分类网络中,这里的问题不大,把训练的规模扩增到几十台机器不难。但PixelCNN却对过时的梯度极其敏感,在它的异步随机梯度下降算法内增加机器几乎没有任何收益。

    另一个方法,是用同步随机梯度下降算法(Synchronous SGD)。使用这一方法,机器在每个时间步长内都进行同步,且每台机器的梯度都会被平均。它与随机梯度下降算法在数学上是相同的。更多的机器会增大批尺寸。但同步随机梯度下降算法(Sync SGD)允许各机器使用更小、更快的批尺寸,从而来增加每秒的步数(steps/sec)。

    然而,同步随机梯度下降算法也有它自己的问题。首先,它需要大量的机器经常进行同步,这必然导致停机时间的增加。其次,除非将每台机器的批尺寸设为1,否则它无法通过增加机器来增加每秒训练的步数。最终,我发现简单的设置是用一台机器安装8个GPU来使用同步随机梯度下降算法——但完成训练仍需花上数天的时间。

    采用大规模计算的另一个办法,是进行规模更大的超参数搜索。如何来确定所用的批尺寸?把它们全都试一遍。在找到论文中所用的配置前,我尝试过数百种配置。

    如何定量评估结果,则是另外一个难题。如何才能证明我们的图像比基准模型好?衡量超分辨率质量的典型方法,是对比强化图像与原始图像的对应像素点之间的距离(峰值信噪比,PSNR)。虽说本模型输出的脸部图像在质量上明显更好,但在像素对比上,平均看来它们还不如基准模型所输出的模糊图像。我们尝试用PixelCNN本身的相似度测量来证明我们的样本比基准版本有着更高的概率值,但同样失败了。最后,我们把这项任务众包给人类评估员——询问他们哪些图像看上去更真实。这倒奏效了。

    具体的结果请查看这篇论文:超分辨率的像素递归

    https://arxiv.org/abs/1702.00783

    PixColor: 关于着色的尝试

    PixColor输出的双色模式

    Slim的创造者Sergio Guadarrama一直在尝试给图像着色。他跟我说过一个试验:用分量接口(该接口中图像的灰度、颜色相互分离)获取一张224×224×3的图像,将其颜色通道降至28×28×2的超低分辨率,然后用双线性插值法再把颜色通道放大,所得图像与颜色分辨率很高的原始图像相比几乎没有差别。

    图3:你需要的只是一些颜色。顶行是原始彩色图像。中间行是降低采样率后的真实色度图像,尺寸缩小至28像素。底行是双线性提高中间行的采样率并结合原始灰度图像的结果。

    这表明,把问题变成仅预测低分辨率颜色,我们就可以简化着色问题。我原本已准备好彻底放弃PixelCNN了,因为它显然无法放大小图像,但用来生成28×28×2的图像还是很可行的。通过将颜色数值简化为32个数字而非256,我们进一步简化了着色问题。

    Sergio构建了一个“改进的”网络,它能够清理低分辨率颜色的输出,并将溢出边界的颜色推回至正确位置——使用前馈式图像对图像卷积神经网络进行训练,损失仅为L2。我们还用一个预训练好的ResNet作为条件网络,用以消解额外的损耗项需求,毕竟在超分辨率项目中我们已经用过这样的损耗项。

    使用这些方法后,无论是众包评估还是用颜色直方图相交评估,我们都能得出ImageNet上最好的结果。事实证明,经过正确训练的PixelCNN可以很好地模拟图像统计数据,不发生任何模式崩溃。

    图7:实验室颜色空间中的颜色通道的边缘统计数据。左:每种方法的直方图以蓝色显示,ImageNet的测试数据集直方图以黑色显示。右图:颜色通道的直方图相交。

    由于模型为每个灰度输入的可能着色声明了一个概率分布,我们可以对该分布进行多次取样,以获取同一输入的不同着色。下图用结构相似度(SSIM) 很好地展示了分布的多样性:

    图8:为证明我们的模型可生成不同的样本,我们用多尺度SSIM对比了同一输入的两种输出。上图显示了ImageNet测试数据集的SSIM距离直方图。图中在多个SSIM间距上分别显示了每对图像。SSIM值为1表示两张图像完全相同。

    该模型离远未完美。ImageNet尽管庞大,但不能代表所有的图像。而该模型在处理非ImageNet图像时并不理想。我们发现,真实的黑白照片(不同于彩色转化为灰度的黑白照)会得出不同的统计数据,并能出现很多彩色照片中所没有的物体。比如,Model T汽车的彩色照片不多,ImageNet图像集中可能一张都没有。采用更大的数据集和更好的数据扩增,也许能简化这些问题。

    想了解图像质量的话,可以来看看这些图:

    • 处于我们模型处理中间阶段的一小组非常难处理的图像

    http://tinyclouds.org/residency/step1326412_t100/index.html

    • 用于我们模型的ImageNet随机测试数据集图像

    http://tinyclouds.org/residency/rld_28px3_t100_500_center_crop_224/

    作为对比,下面是用其他算法来处理同一ImageNet测试数据集的结果:

    • 给图像着色!

    http://tinyclouds.org/residency/ltbc_500_center_crop_224/index.html

    • 彩色图像着色

    http://tinyclouds.org/residency/cic_500_center_crop_224/index.html

    • 自动着色的学习表示

    http://tinyclouds.org/residency/lrac_500_center_crop_224/index.html

    最后,完整的细节都在我们的论文中: PixColor: Pixel Recursive Colorization

    https://arxiv.org/abs/1705.07208

    失败与未报告的实验

    这一年期间,我曾短暂着迷过许多业余的小项目,尽管它们都失败了……接下来我会简单来描述其中的几个:

    大数的素因数分解

    素因数分解一向都是个大难题。但即便是如今,我们仍在不断发现有关素数分解的新问题。如果为深度神经网络提供足够的实例,它能不能找出一些新东西?Mohammad和我尝试过两种方法。他修改了Google机器翻译的seq2seq神经模型,该模型把一个半素大数的整数序列作为输入,并将其素因素预测为输出。我则使用一个较为简单的模型,它将定长整数作为输入,并用几个全连接层来预测输入的分类:素数或合数。这两种方法都只学到了最为明显的规律(如果尾数为0,那它就不是素数!),我们只能抛弃这个想法。

    Adversarial Dreaming

    受Michael Gygli的项目启发,我想探究一下鉴别器能否充当它自己的生成器。为此,我构建出一个简单的二元分类卷积神经网络来判断输入的真假。为了生成图像,你需要给出噪点并让它使用梯度来更新输入(有时被称为deep dreaming),令该网络把“真实”类别最大化。该模型通过交替生成“假”实例来进行训练,而后跟典型的GAN鉴别器一样,通过升级权重来区分真假实例。

    我的想法是,鉴于更少的架构决策,该网络相比一般的GAN可能更容易训练。事实上,它用MNIST确实可以工作。下图中每一栏都在展示:不同的噪音图像被逐渐推向红色MNIST数值的情形。

    但我没法让它在CIFAR-10数据集上工作,并且它的实际意义也极为有限。这遗憾了,我相信 "Adversarial Dreaming" 会是一个很酷的论文题目。

    使用PixelCNN来训练生成器

    PixelCNN生成样本的时间过长,这让我很沮丧。于是,我就想试试能不能用一个预训练的PixelCNN训练出前馈式图像对图像卷积神经网络(8x8至32x32尺寸的LSUN卧室图片集)。我所设置的训练方法是:在前馈式网络的输出上进行自动回归。在PixelCNN下更新权重以便将概率最大化。它用这样的线条生成了非常奇怪的图像:

    对异步随机梯度下降算法的修改探索

    如前文所述,很多模型都不适用于异步随机梯度下降算法。最近,一篇名为DCASGD论文提出了一种解决过时梯度问题的可能方法:在机器开始步进去应用它们权重的前,在权空间(weight space)使用差分向量。这种方法可大大减少每一个人的训练时间。不幸的是,我没能在TensorFlow上重复他们的结果,也无法实现我所想出的几个类似想法。这里可能有Bug。(如果想获取我的实现方法,请通过内部渠道联系我)

    想法和总结

    聊了这么多的技术,我大致做些总结,也算是比较深的心得体会吧。

    作为软件工程师,我在机器学习方面并没有什么经验。但基于过去一年对深度学习的研究,我来分享一下在该领域的总体看法,及其同范围更广的软件领域之间的关系。

    我坚信,机器学习将改变所有行业,并最终改善每个人的生活,许多行业都会因机器学习所提供的智能预测而受益。

    对于我,我在这个项目中,最初的目标是,在不久的将来,所有人都可以看到查理·卓别林这类老电影的4K版。

    不过,我确实发现,这一模型的构建、训练和调试都相当困难。当然,大部分的困难是由于我缺乏经验,这也表明有效训练这些模型是需要相当丰富的经验的。

    我的工作集中在机器学习最为容易的分支上:监督式学习。但即便有着完美的标注,模型开发仍十分困难。似乎,预测的维度越大,构建模型所花的时间就越长(例如:花大把的时间进行编程、调试和训练)。

    因此,我推荐所有人在开始时都尽可能简化和限制你的预测

    举一个我们在着色实验中的例子:我们在开始时试图让模型预测整个的RGB图像,而非只预测颜色通道。我们认为,神经网络很容易就能处理好灰度图(intensity image)并输出出来,因为我们使用的是跳跃连接(skip connection)。只预测颜色通道依然能改进性能。

    如果我以主观、本能的方式使用“工作”来描述软件:图像分类工作起来似乎很稳健。生成式模型几乎很少能工作,人们也不太了解这种模型。GAN能输出很好地图像,但是构建起来几乎是不可能的——我的经验是,对架构作出任何小改动都有可能使它无法工作。我听说强化学习更加困难。但因经验不足,我对递归神经网络不予置评。

    但是,随机梯度下降算法工作起来是太过于稳定,即使是严重的数学错误也不会让它彻底失败,仅稍微有损于性能。

    因为训练模型经常需要花费很多天,这是一个非常缓慢的 修改—运行 循环。

    测试文化尚未完全兴起。训练模型时我们需要更好的评断方法,网络的多个组成部分需要维持特定的均值和变量,不能过度摆动或者留在范围内。机器学习漏洞使系统的heisenbugs能特别轻松地通过测试。

    并行化能带来的好处很有限。增加计算机数后,大规模的超参数搜索会变得更加容易,但是理想情况下,我们会设计不用仔细调试也能正常工作的模型。(实际上,我怀疑超参数搜索能力有限的研究人员将不得不设计出更好的模型,因此他们设计出的模型更加稳定)。

    不幸的是,对于很多模型而言,异步随机梯度下降算法并没有什么用处——更加精确的梯度通常用处不大。这就是为什么 DCASGD 的研究方向很重要的原因。

    从软件维护的角度看,关于如何组织机器学习项目大家都鲜有共识。

    就像是Rail出现之前的网站:一群随机PHP脚本,商业逻辑和标记符号乱混一气。在TensorFlow项目中,数据管道、数学和超参数/配置管理无组织地混为一团。我认为我们还未发现精美的结构/组织。(或者说是还未重新发现,就像DHH重新发现并普及 MVC那样。)我的项目结构一直在进步,但是我不会认为它是精美的。

    框架会继续快速进化。我开始使用的是Caffe,不得不称赞TensorFlow带来的好处。现在,PyTorch 和 Chainer之类的项目使用动态计算图形取悦客户。漫长的修改—行循环是开发更好模型的主要阻碍——我怀疑能优先实现快速启动和快速评估的框架最终会取得成功。尽管拥有TensorBoard和iPython之类的有用工具,但是检查模型在训练期间的活动仍然很难。

    论文中的信噪比很低。但是需要改进的空间还很大。人们通常不会坦率承认他们模型的失败之处,因为学术会议更看重的是准确度而不是透明度。我希望学术会议能接受提交博客文章,并要求开源实现)。Distill 在这方面的努力值得称赞。

    对机器学习而言,这是一个令人激动的时代。各个层面上有大量工作需要完成:从理论端到框架端,还有很多值得改进的空间。它几乎和因特网的诞生一样令人激动。加入这场技术革命吧!

    这是你的机器学习系统吗?

    是啊!将数据倒到这一大堆线性代数中,然后在另外一端收集答案。

    如果答案是错误的呢?

    只管搅动这堆线性代数,直到结果开始看起来正确为止。


    展开全文
  • nodejs简介 1.nodejs的诞生 学习一门技术之前,有必要了解该技术是如何诞生的? nodejs是ryan dahl(nodejs 之父)于2009年发布的。 (ryan dahl) ry...

    nodejs简介

    1.nodejs的诞生

    学习一门技术之前,有必要了解该技术是如何诞生的?

    nodejs是ryan dahl(nodejs 之父)于2009年发布的。

    clipboard.png

                                       (ryan dahl)
                                       

    ryan dahl
    2004 其在纽约罗切斯特大学数学系读博,研究一些分型、分类的研究。

    2006 可能是厌倦的无聊的读博生活,产生了“世界那么大,我想去看看”的想法,做出了退学的决定,去到了智利的一个小镇。

    这个时候他开始学习网站的开发,经过2年时间,成为高性能web专家,从接开发到为客户解决性能问题的专家。他明白,解决性能问题的关键是:事件驱动、异步I/O 。期间他尝试用ruby、c、luo,最终都失败了。
    原因:
    ruby:虚拟机性能太差
    c:性能虽高,门槛高,业务开发效率低
    luo:天生的同步I/O

    在他要放弃时,google在新一轮的浏览器大战中胜出,V8引擎到来了(感谢谷歌) ,V8完全满足期要求。于是他把V8搬到了后台,使得js的触角触到了服务器。

    2009年2月,把项目定义为‘node’;同年5月,向外界宣布这个项目;年底,在柏林的jsconf eu 大会上进行nodejs的演讲,之后nodejs开始流行..

    展开全文
  • 最近刚好有朋友在问Node.js多线程的问题,我总结了一下,可以考虑使用源码包里面的worker_threads...追究其本质,NodeJs实际上使用了两种不同的线程,一个是用于处理循环事件的主线程一个是工作池(Worker pool)里面.

    最近刚好有朋友在问Node.js多线程的问题,我总结了一下,可以考虑使用源码包里面的worker_threads或者第三方的模块来实现。
    首先明确一下多线程在Node.js中的概念,然后在聊聊worker_threads的用法。天生异步,真心强大。

    1. Node.js多线程概述
      有人可能会说,Node.js虽然是单线程的,但是可以利用循环事件(Event Loop)l来实现并发执行任务。追究其本质,NodeJs实际上使用了两种不同的线程,一个是用于处理循环事件的主线程一个是工作池(Worker pool)里面的一些辅助线程。关于这两种线程主要功能和关系如图1所示。

    图1 Node.js线程图
    所以从本质上来讲,NodeJs并不是真正的原生多线程,而是利用循环事件来实现高效并发执行任务。要做到真正的多线程,需要依赖其他模块或者第三方库。
    2. Worker_threads是Node.js官方推荐的实现真正多线程的模块,有官方技术团队进行长期维护。Worker_threads不需要单独安装,它位于Node.js源码中,具体位置是lib/worker_threads.js。worker_threads模块允许使用并行执行JavaScript的线程,使用也非常方便,只需要引入该模块即可,代码如下。
    const worker = require('worker_threads');
    与child_process或cluster不同,worker_threads可以共享内存。它们通过传输ArrayBuffer实例或共享SharedArrayBuffer实例来实现。
    官网上给了一个完整的例子,如下所示。

    const {
        Worker, isMainThread, parentPort, workerData
    } = require('worker_threads');
    
    if (isMainThread) {
        module.exports = function parseJSAsync(script) {
            return new Promise((resolve, reject) => {
                const worker = new Worker(__filename, {
                    workerData: script
                });
                worker.on('message', message => console.log(message));
                worker.on('error', reject);
                worker.on('exit', (code) => {
                    if (code !== 0)
                        reject(new Error(`Worker stopped with exit code ${code}`));
                });
            });
        };
        
    } else {
        const { parse } = require('som-parse-libary');
        const script = workerData;
        parentPort.postMessage(parse(script));
    }
    

    笔者对以上代码开始解析,重点概念如下所示:
    Worker该类代表一个独立的js执行线程。
    isMainThead一个布尔值,当前代码是否运行在Worker线程中。
    parentPortMessagePort对象,如果当前线程是个生成的Worker线程,则允许和父线程通信。
    workerData一个可以传递给线程构造函数的任何js数据的的复制数据。
    Worker_theads还提供了很多实用的API,整理如下所示。
    1.worker.getEnvironmentData(key)
    可以获取环境变量,先使用setEnvironmentData来设置环境变量,然后再使用g
    etEnvironmentData来获取。
    举一个简单的例子,代码如下所示。

    const {
      Worker,
      isMainThread,
      setEnvironmentData,
      getEnvironmentData,
    } = require('worker_threads');
    
    if (isMainThread) {
      setEnvironmentData('Hi', 'Node.js!');
      const worker = new Worker(__filename);
    } else {
      console.log(getEnvironmentData('Hi'));.
    }
    
    执行这段代码,可以在控制台打印出“Node.js”字符串。
    
    1. isMainThread
      isMainThread可以用来判断该进程是不是主线程,如果是主线程,则返回true,否则返回false。下面编写一个嵌套worker的代码,用于展示。
    const { Worker, isMainThread } = require('worker_threads');
    
    if (isMainThread) {
        console.log("This is  a main thread
    ");
        // This re-loads the current file inside a Worker instance.
        new Worker(__filename);
    } else {
        console.log('Inside Worker!');
        console.log(isMainThread);  // Prints 'false'.
    }
    
    1. MessageChannel和相关用法
      MessageChannel是worker_threads提供的一个双向异步的消息通信信道。下面这段代码就展示了两个MessagePort对象互相传递消息的过程,我们如果想主动结束某个Channel,那么可以使用close事件来完成。
    const {MessageChannel}  = require('worker_threads');
    
    const {port1, port2} = new MessageChannel();
    
    // port1给port2发送信息
    port1.postMessage({carName: 'BYD'});
    
    port2.on('message', (message) => {
        console.log("I receive message is ", message);
    })
    
    // port2给port1发送信息
    port2.postMessage({personality: "Brave"});
    port1.on('message', (message) => {
        console.log("I receive message is ", message);
    });
    

    运行上面的代码,可以在控制台看到如下输出:

    I receive message is  { personality: 'Brave' }
    I receive message is  { carName: 'BYD' }
    

    port.on(‘message’)方法是利用被动等待的方式接收事件,如果想手动接收信息可以使用receiveMessageOnPort方法,指定从某个port接收消息,如下所示。

    const { MessageChannel, receiveMessageOnPort } = require('worker_threads');
    const {port1, port2} = new MessageChannel();
    port1.postMessage({Name: "freePHP"});
    
    let result = receiveMessageOnPort(port2);
    console.log(result);
    let result2 = receiveMessageOnPort(port2);
    console.log(result2);
    

    运行上面的代码,可以得到如下输出。

    { message: { Name: 'freePHP' } }
    undefined
    

    从结果可以看出,receiveMessageOnPort可以指定从另一个MessagePort对象获取消息,是一次消耗消息。
    实际工作中,我们不可能只使用单个线程来完成任务,所以需要创建线程池来维护和管理worker thread对象。为了简化线程池的实现,假设只会传递一个woker脚本作为参数,具体实现如下所示。需要单独安装async_hooks模块,它用于异步加载资源。

    const { AsyncResource } = require('async_hooks'); // 用于异步加载资源
    const { EventEmitter } = require('events');
    const path = require('path');
    const { Worker } = require('worker_threads');
    
    const kTaskInfo = Symbol('kTaskInfo');
    const kWorkerFreedEvent = Symbol('kWorkerFreedEvent');
    
    class WorkerPoolTaskInfo extends AsyncResource {
        constructor(callback) {
            super('WorkerPoolTaskInfo');
            this.callback = callback;
        }
    
        done(err, result) {
            this.runInAsyncScope(this.callback, null, err, result);
            this.emitDestroy();  // 只会被执行一次
        }
    }
    
    class WorkerPool extends EventEmitter {
        constructor(numThreads) {
            super();
            this.numThreads = numThreads;
            this.workers = [];
            this.freeWorkers = [];
    
            for (let i = 0; i < numThreads; i++)
                this.addNewWorker();
        }
    
        /**
         * 添加新的线程
         */
        addNewWorker() {
            const worker = new Worker(path.resolve(__dirname, 'task2.js'));
            worker.on('message', (result) => {
                // 如果成功状态,则将回调传给runTask方法,然后worker移除TaskInfo标记。
                worker[kTaskInfo].done(null, result);
                worker[kTaskInfo] = null;
                //
                this.freeWorkers.push(worker);
                this.emit(kWorkerFreedEvent);
            });
            worker.on('error', (err) => {
                // 报错后调用回调
                if (worker[kTaskInfo])
                    worker[kTaskInfo].done(err, null);
                else
                    this.emit('error', err);
                // 移除一个worker,然后启动一个新的worker来代替当前的worker
                this.workers.splice(this.workers.indexOf(worker), 1);
                this.addNewWorker();
            });
            this.workers.push(worker);
            this.freeWorkers.push(worker);
            this.emit(kWorkerFreedEvent);
        }
    
        /**
         * 执行任务
         * @param task
         * @param callback
         */
        runTask(task, callback) {
            if (this.freeWorkers.length === 0) {
                this.once(kWorkerFreedEvent, () => this.runTask(task, callback));
                return;
            }
    
            const worker = this.freeWorkers.pop();
            worker[kTaskInfo] = new WorkerPoolTaskInfo(callback);
            worker.postMessage(task);
        }
    
        /**
         * 关闭线程
         */
        close() {
            for (const worker of this.workers) {
                worker.terminate();
            }
        }
    }
    
    module.exports = WorkerPool;
    

    其中task2.js是定义好的一个计算两个数字相加的脚本,内容如下。

    const { parentPort } = require('worker_threads');
    parentPort.on('message', (task) => {
      parentPort.postMessage(task.a + task.b);
    });
    

    调用这个线程池非常简单,用例如下所示。

    const WorkerPool = require('./worker_pool.js');
    const os = require('os');
    
    const pool = new WorkerPool(os.cpus().length);
    
    let finished = 0;
    for (let i = 0; i < 10; i++) {
      pool.runTask({ a: 42, b: 100 }, (err, result) => {
        console.log(i, err, result);
        if (++finished === 10)
          pool.close();
      });
    }
    
    展开全文
  • nodejs 创建文件

    万次阅读 2019-01-15 11:31:47
    最近准备横向发展一下自己,了解一下被炒的很热但实际市场用的又不是特别多,但是作为一个合格的前端开发人员必须掌握的知识——nodejs。我会将一些平时学习基础资料整理,顺便当做笔记记录一下,和大家一起分享学习...

    创建文件

    专注前端4年,一直以来奋战在搬砖的一线,从事着后台系统、PC端、移动端的开发,然而市场的发展速度确实让人有些许瞠目结舌,前端框架层出不穷,无论是 JS 框架(react.js、vue.js 、angular.js 、react-native、veex……) 还是 UI 框架(Bootstrap、elementUI、iView、vuetify、at-ui、flutter……) 都让我们的大脑不够用,并且每个公司的技术选型和业务不同,所选择的框架都不同,然而苦逼的我们没有办法学会所有,只有在入职公司要啥,我们就快速的学习啥,枪指哪儿,我们就打哪儿。最近准备横向发展一下自己,了解一下被炒的很热但实际市场用的又不是特别多,但是作为一个合格的前端开发人员必须掌握的知识——nodejs。我会将一些平时学习基础资料整理,顺便当做笔记记录一下,和大家一起分享学习。

    fs 模块创建文件

    // mkdir.js
    const mkdir = require("./module");
    mkdir("demo/test", err => {
      console.log(err);
    });
    
    mkdir("demo01", err => {
      console.log(err);
    });
    
    mkdir("demo02/demo03/demo04", err => {
      console.log(err);
    });
    

    自定义创建文件 modules

    // modules.js
    const fs = require("fs");
    const path = require("path");
    
    function mkdirs(pathname, callback) {
      // 需要判断是否是绝对路径(避免不必要的bug)
      pathname = path.isAbsolute(pathname) ? pathname : path.join(__dirname, pathname);
      // 获取相对路径
      pathname = path.relative(__dirname, pathname);
      let floders = pathname.split(path.sep); // path.sep 避免平台差异带来的bug
      let pre = "";
      floders.forEach(floder => {
        try {
          // 没有异常,文件已经创建,提示用户改文件已经创建
          let _stat = fs.statSync(path.join(__dirname, pre, floder));
          let hasMkdir = _stat && _stat.isDirectory();
          if (hasMkdir) {
            callback && callback(`文件${floder}已经存在,不能重复创建,请重新创建`);
          }
        } catch (error) {
          // 抛出异常,文件不存在则创建文件
          try {
            // 避免父文件还没有创建的时候先创建子文件所出现的意外bug,这里选择同步创建文件
            fs.mkdirSync(path.join(__dirname, pre, floder));
            callback && callback(null);
          } catch (error) {
            callback && callback(error);
          }
        }
        pre = path.join(pre, floder); // 路径拼合
      });
    }
    
    module.exports = mkdirs;
    

    小结

    注释已经写得很清楚了,就不过多的解释了,仅仅是学习笔记,相对简单易懂,希望各位看官可以提供更好的方法以供参考,不喜勿喷……

    其它前端学习资料和文章如下


    其它前端性能优化:

    前端技术架构体系(没有链接的后续跟进):

    其它相关

    展开全文
  • nodejs之全局对象

    2017-08-18 09:55:34
    Nodejs全局对象 javascript中有一个特殊的对象,称为全局对象。它及其所有的属性都可以在程序的任何地方访问,即全局变量 在浏览器javascript中,通常window是全局对象,而nodejs中的全局对象是global,所有全局...
  • nodejs之process对象

    2018-12-19 16:06:34
    这些用来生成子进程,使拥有和进程有相同的参数 console.log(process.execArgv); 【process.execPath】  开启当前进程的执行文件的绝对路径 console.log(process.execPath);//D:\nodejs\...
  • nodejs之多进程

    2014-06-25 19:11:52
    最近感觉压力有点大,来看看nodejs的书,好让自己轻松一下。
  • Nodejs之child_process

    2020-01-10 15:17:52
    2、返回子进程ChildProcess对象,并内置为一个额外的IPC通信通道,允许消息在进程和子进程之间来回传递,子进程独立于进程; 3、modulePath 要在node子进程中运行的模块,由于是 node.js 的进程,所以可以是 ....
  • nodejs之require方法

    2017-11-06 17:49:05
    一、require() 的基本用法 下面的内容翻译自《Node使用手册》。 当 Node 遇到 require(X) 时,按下面的顺序处理。 (1)如果 X 是内置模块(比如 require('http'))  ... a.... b.... a. 根据 X 所在的模块
  • fs模块是nodejs的核心模块一,只要安装了nodejs,就可以直接使用fs模块,不需要单独安装。引入fs模块非常简单: let fs = require('fs'); 接下来就可以调用fs模块的相关接口直接读写文件系统。 fs模块主要提供了...
  • nodejs

    2019-03-09 22:54:58
    nodejs : 单线程 非阻塞I/O 优点: 节约内存 节约上下文切换的时间 锁的问题 ,并发资源的处理( java里面的概念 并发的时候对资源加锁限制其他进程对其的访问 ) 缺点 : 一个线程崩了整个程序就挂了 多线程是...
  • NodeJS之child_process模块

    千次阅读 2019-10-27 10:05:30
    文章目录1 Child Process 模块1.1 简介1.2 方法1.2.1 exec()1.2.2 execFile()1.2.3 spawn()1.2.4 fork()...子进程的运行结果储存在系统缓存中(最大200KB),等到子进程运行结束以后,主进程再用回调函数读取子...
  • NodeJs之child_process

    2016-11-19 14:43:00
    child_process是NodeJs的重要模块。帮助我们创建多进程任务,更好的利用了计算机的多核性能。 当然也支持线程间的通信。 二.child_process的几个API 异步: child_process.exec(command[, options][, ...
  • Nodejs之http对象

    2014-04-26 16:04:41
    如果有函数嵌套,则叫给函数,最终会把控制权交给全局执行环境,   闭包 如果在function中又有一个函数(3),就是闭包,环境栈就会改变, [’dosomething执行环境‘,’say执行环境‘,'全局执行环境'],每个...
  • nodeJS之进程process对象

    2018-12-24 10:47:00
    这些用来生成子进程,使拥有和进程有相同的参数 console.log(process.execArgv); 【process.execPath】  开启当前进程的执行文件的绝对路径 console.log(process.execPath);//D:\nodejs\node....
  • NODEJS nodejs

    2017-04-06 17:44:00
    Node.js 是一个 构建于 谷歌的 Chrome 浏览器的 V8 引擎上的一个 JavaScript运行时 环境 Node.js可以解析和执行 JavaScript 代码 Node.js uses an event-driven, non-blocking I/O model that makes it ...
  • nodejs之util工具的介绍

    2016-12-09 17:10:36
    //复制对象上所有的方法 util .inherits (MyStream, events .EventEmitter ) ; //对MyStream类添加原型方法 MyStream.prototype.write = function (data) { this .emit( "data" , data); } ...
  • NodeJS

    2016-03-02 18:14:32
    NodeJS基础 什么是NodeJS JS是脚本语言,脚本语言都需要一个解析器才能运行。对于写在HTML页面里的JS,浏览器充当了解析器的角色。而对于需要独立运行的JS,NodeJS就是一个解析器。 每一种解析器都是一个...
  • Ryan Dahl:Node 失误太多无力回天,Deno 前景明朗Node 之父 Ryan Dahl 近日在柏林 JS 大会上发表了主题演讲,这也是 Ryan Dahl 做的第二次关于 JS 的公开演讲,第一次是在 2009 年,当时是宣布 Node 项目诞生,而这...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,269
精华内容 2,107
关键字:

nodejs之父