• 【NodeJS线程】Boss和他的职员们


    >>>【说明】还是一如既往的,这篇文章是从我的个人博客里挪过来的。原文参见:http://www.jscon.co/coding/frontend/nodejs_fork_child_process.html 

             Child Process模块还提供一个用于创建一个也是Node.js的子进程,并提供父子进程具备通信通道的能力,这个方法称为 fork() ,相当于spawn('node', ['./child.js'])。与默认的spawn不同的是,fork会在父进程与子进程直接建立一个 IPC管道 ,用于父子进程之间的通信。
             使用 fork() 的开销在于每个子进程是个全新的V8实例,Node.js文档提到,每一个进程需要花费30ms 启动并占用 10MB 内存,所以能启动多少个子进程取决于计算机有多少内存。

    1、一个例子

    【举例】现在我们模拟Boss向职员分配任务的过程。情景是这样的:Boss现在有三个任务让三个职员去做,一个负责一个。这三个职员不能并行处理这三个任务,只有当完成一个任务后,Boss才会分配给另外一个职员下一个任务。

    (苦逼码农:我的Boss向来让我一个人顶仨~~)

    这里的Boss是父线程,职员是子线程。我们一步一步来完成这个情节的代码。

    【注意】

             fork函数有一个问题,就是它只能运行JavaScript代码,如果你喜欢用CoffeeScript(或者其他任何编译到js的语言),是无法通过fork调用的

             文章《Node.js中的child_process及进程通信》提供了一种方法,不过我没有成功,大家可以试试:

    一个简单的方法是把代码编译到JavaScript再运行,但是很不方便,有没有什么办法呢?

    答案是可以的,还是得回到spawn函数。spawn函数除了接受command, args外,还接受一个options参数。通过把options参数的stdio设为['ipc'],即可在父子进程之间建立IPC管道。例如子进程使用CoffeeScript:

    child_process = require('child_process')
    
    options =
    
      stdio: ['ipc']
    
    child = child_process.spawn 'coffee', ['./child.coffee'], options

      

    因此下面的程序虽是用CoffeeScript编写的,但最后都编译成js文件再运行。

    2、创建基础父子进程

    CoffeeScript代码:

    父进程所在文件:(removed.coffee)

    fork = require("child_process").fork           # 引入fork组件
    
    child = fork "child.js"                         # 生成child子进程
    
    console.log "parent pid:",process.pid
    
     
    
    child.on "message",(m)->              #定义父进程收到子进程发来消息时的动作
    
         console.log "receive message:",m
    
         setTimeout ()->
    
             child.kill "SIGINT"            #子进程完成任务了,过3秒后杀死child进程
    
         ,3000
    
     
    
    child.on 'exit',(code,sig)->            #当子进程退出时所触发的动作
    
         console.log code,":",sig
    
     
    
    child.send "task1"                    # OK,开始向子进程发送消息!

      

    子进程所在文件:(child.coffee)

    console.log "==========================="
    
    console.log "child pid:",process.pid
    
     
    
    process.on 'message',(m)->            #定义子进程收到父进程发来消息时的动作
    
         console.log process.pid,'start new task:',m
    
         console.log "start process.....Done"
    
         console.log "communiate with parent...."
    
         process.send "done"               #子进程完成任务后,向父进程汇报

     上面的代码大部分是装饰形的log代码,其中有注释的代码才是关键的。 

    代码解释:

            上面的代码讲了这么一个故事:Boss通过fork生成一个子进程,给子进程喊了一句“task1”后,子进程就屁颠屁颠得去做任务。子进程完成后向父进程汇报“done”,父进程得知职员办事给力,一高兴,3秒钟之后kill了这个进程,然后,就没有然后了….

    (这是在拍《风云》的节奏啊…..)

    运行结果:

    创建基础父子进程 

    3、完成进程管理

             上面的代码只生成了一个子进程,说好的三个呢?有了上面的基础代码,完成题目的要求就比较简单了,这次我们只稍微修改父进程所在的脚本即可:

    CoffeeScript代码:

    父进程所在文件:(removed.coffee)

    fork = require("child_process").fork
    
    child = fork "child.js"
    
    console.log "parent pid:",process.pid
    
     
    
    rebuild = (child)->                    # 将两个事件侦听器包装秤rebuild函数
    
         child.on "message",(m)->        
    
             console.log "receive message:",m
    
             setTimeout ()->
    
                  child.kill "SIGINT"      # kill 子进程
    
                  child = fork "child.js"   # 重生成子进程         
    
                  rebuild(child)            # 给生成的子进程绑定侦听器
    
                  child.send "task1"       # 父进程给子进程发消息
    
             ,3000           
    
     
    
         child.on 'exit',(code,sig)->
    
             console.log code,":",sig
    
     
    
    rebuild(child)
    
    child.send "task1"

     代码解释:

           这是“稍微修改”的意思?果然呵,男人都是骗子!

    通过现象看本质,看一下修改的步骤:

    ①  这里把前一部分的事件侦听器代码(就是那两个 on 什么的啦)包装了一下,命名成一个 rebuild函数 。这里的重点是然后再在递归使用。因为我们在每一次任务完成后会kill掉子进程,所以被kill掉的子进程的侦听器自然跟着消亡,所以每次fork之后就需要重新绑定事件(就是那两个 on 什么的啦):

                                child.kill "SIGINT"
    
                                child = fork "child.js"                         
    
                                rebuild(child)
    
                                child.send "task"+count

     也许通过这四行代码你就会发现,俺家之所以将该函数取名为rebuild函数是有原因的:凤凰涅槃,浴火重生;第一句kill上次的child,接着fork一个新的child,同时rebuild出该新函数的事件侦听器(就是那两个 on 什么的啦),接着父元素有向这个新生的child发出“task1”信号…. 

    ②  猜想的出来执行之后的效果么?如果认为会出现刷屏代码的话,说明你已经懂了 

    运行结果: 

    [caption id="" align="aligncenter" width="397"]无尽地循环执行任务 无尽地循环执行任务 [/caption]

            很明显上面的代码不是我们所想要的,我们只要三个子进程即可!好吧,杜绝超生,那只能计划生育了。加个count变量,加个 if… else… 语句就行了。不多说,翠花,上代码: 

    CoffeeScript代码:

    父进程所在文件:(removed.js)

    fork = require("child_process").fork
    
    child = fork "child.js"
    
    console.log "parent pid:",process.pid
    
     
    
    count = 0
    
    rebuild = (child)->
    
         child.on "message",(m)->
    
             count += 1
    
             if count < 3         
    
                  console.log "receive message:",m
    
                  setTimeout ()->
    
                       child.kill "SIGINT"
    
                       child = fork "child.js"                
    
                       rebuild(child)
    
                       child.send "task"+count
    
                  ,3000
    
             else
    
                  console.log "receive message:",m
    
                  console.log "task over"
    
                  setTimeout ()->
    
                       child.kill "SIGINT"
    
                  ,3000           
    
     
    
         child.on 'exit',(code,sig)->
    
             console.log code,":",sig
    
     
    
    rebuild(child)
    
    child.send "task"+count

    运行结果:

    [caption id="" align="aligncenter" width="364"]完成有限的进程管理 完成有限的进程管理[/caption]

    4、传递参数

            如果仅仅这样单纯的管理的话,父子进程(为啥我总打成“父子进城”呢??)其实不过尔尔,花拳绣腿罢了。一般的情形父进程往往会附带一些详细的指令给子进程,比如“去城里买些菜回来”~~

            传递的message可以是JSON格式,真的是JSON,不骗你,不带解析的!

    CoffeeScript代码:

    父进程所在文件 removed.coffee 修改的地方较少,只有两处但很关键:

    child.send "task"+count

     改成:

    child.send {where:"城市"+count,fn:"buy"}

     没错,传送的就是JSON格式的数据。

    子进程所在文件:(child.coffee)

    console.log "==========================="
    
    console.log "child pid:",process.pid
    
    exports.buy = ()-> "买菜"              #定义buy函数
    
     
    
    process.on 'message',(m)->             
    
         console.log process.pid,'start new task:',m
    
    console.log "",m.where,",",exports[m.fn]()   #字符串和函数
    
         console.log "start process.....Done"
    
         console.log "communiate with parent...."
    
         process.send "done"    

     这里我想表达的意思有两个:

    ①  父子线程间的通信可以是JSON格式的,不用解析可以直接使用;

    ②  你看出来了么,“buy”是父进程传给子进程的一个字符串量,但在子进程中调用了其buy函数~~ 

    运行结果: 

    [caption id="" align="aligncenter" width="454"]进程通信中的传递参数 进程通信中的传递参数[/caption]

    故事的结尾,为了响应Boss的号召,这三个职员就真的去三个城市买菜,卖完菜后,Boss很十分感动, 就kill 它们了,然后,就没有然后了….

    (汗…………………………………………………………….. The End)

  • 相关阅读:
    vscode常用快捷键及常用设置
    markdown语法笔记
    Recoil 了解一下
    url的组成
    webpack基础配置
    Unity3D 游戏引擎之详解游戏开发音频的播放
    未能加载文件或程序集“AspNetPager”或它的某一个依赖项。参数错误
    Windows* 8商店与桌面应用开发
    unity3d阶段性学习脚本代码(2个是摄像机跟随(2D游戏中的),1个是角色跳跃移动脚本)
    unity3d与web交互的方法
  • 原文地址:https://www.cnblogs.com/boychenney/p/nodejs_fork_child_process.html
Copyright © 2020-2023  润新知