美文网首页JS开发技术
js的单线程和多进程

js的单线程和多进程

作者: 狐尼克朱迪 | 来源:发表于2016-10-14 10:40 被阅读0次

    概述

    现行的软件架构主要有两种:单进程多线程(如:memcached、redis、mongodb等)和多进程单线程(nginx、node)。
    单进程多线程的主要特点:

    • 快:线程比进程轻量,它的切换开销要少很多。进程相当于函数间切换,每个函数拥有自己的变量;线程相当于一个函数内的子函数切换,它们拥有相同的全局变量。
    • 灵活: 程序逻辑和控制方式简单,但是锁和全局变量同步比较麻烦。
    • 稳定性不高: 由于只有一个进程,其内部任何线程出现问题都有可能造成进程挂掉,造成不可用。
    • 性能天花板:线程和主程序受限2G地址空间;当线程到一定数量后,即使增加cpu也不能提升性能。

    多进程单线程的主要特点:

    • 高性能:没有频繁创建和切换线程的开销,可以在高并发的情况下保持低内存占用;可以根据CPU的数量增加进程数。
    • 线程安全:没有必要对变量进行加锁解锁的操作
    • 异步非阻塞:通过异步I/O可以让cpu在I/O等待的时间内去执行其他操作,实现程序运行的非阻塞
    • 性能天花板:进程间的调度开销大、控制复杂;如果需要跨进程通信,传输数据不能太大。

    事实上异步通过信号量、消息等方式早就存在操作系统底层,但是一直没有能在高级语言中推广使用。
    Linux Unix提供了epoll方便了高级语言的异步设计。epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们;libevent和libev都是对epoll的封装,nginx自己实现了对epoll的封装。

    浏览器

    在支持html5的浏览器里,可以使用webworker来将一些耗时的计算丢入worker进程中执行,这样主进程就不会阻塞,用户也就不会有卡顿的感觉了。

    <!DOCTYPE html>
        <head>
            <title>worker</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
            <script>
                function init(){
                    //创建一个Worker对象,并向它传递将在新进程中执行的脚本url
                    var worker = new Worker('./webworker.js');
                    //接收worker传递过来的数据
                    worker.onmessage = function(event){
                        document.getElementById('result').innerHTML+=event.data+"<br/>" ;
                    };
                };
            </script>
        </head>
        <body onload = "init()">
            <div id="result"></div>
        </body>
    </html>
    
    // webworker.js
    var i = 0;
    function timedCount(){
        for(var j = 0, sum = 0; j < 100; j++){
            for(var i = 0; i < 100000000; i++){
                sum+=i;
            };
        };
        //将得到的sum发送回主进程
        postMessage(sum);
    };
    //将执行timedCount前的时间,通过postMessage发送回主进程
    postMessage('Before computing, '+new Date());
    timedCount();
    //结束timedCount后,将结束时间发送回主进程
    postMessage('After computing, ' +new Date());
    

    Node

    Nodejs通过其内置的cluster模块实现多进程。cluster是对child_process进行了封装,目的是发挥多核服务器的性能;pm2 是当下最热门的带有负载均衡功能的 Node.js 应用进程管理器。实际开发时,我们不需要关注多进程环境。

    进程模型

    Node的多进程模型是一个主master多个从worker模式,master的职责如下:

    • 接收外界信号并向各worker进程发送信号
    • 监控woker进程的运行状态,当woker进程退出后(异常情况下),会自动重新启动新的woker进程(进程守护)。
    盗用图片..

    如果只是简单的fork几个进程,多个进程之间会竞争 accpet 一个连接,产生惊群现象,效率比较低。同时由于无法控制一个新的连接由哪个进程来处理,必然导致各 worker 进程之间的负载非常不均衡。

    IPC

    注: 这部分是从当我们谈论 cluster 时我们在谈论什么(下)copy而来

    Node.js 中父进程调用 fork 产生子进程时,会事先构造一个 pipe 用于进程通信。

      new process.binding('pipe_wrap').Pipe(true);
    

    构造出的 pipe 最初还是关闭的状态,或者说底层还并没有创建一个真实的 pipe,直至调用到 libuv 底层的uv_spawn, 利用 socketpair 创建的全双工通信管道绑定到最初 Node.js 层创建的 pipe 上。
    管道此时已经真实的存在了,父进程保留对一端的操作,通过环境变量将管道的另一端文件描述符 fd 传递到子进程。

      options.envPairs.push('NODE_CHANNEL_FD=' + ipcFd);
    

    子进程启动后通过环境变量拿到 fd

      var fd = parseInt(process.env.NODE_CHANNEL_FD, 10);
    

    并将 fd 绑定到一个新构造的 pipe 上

      var p = new Pipe(true);
      p.open(fd);
    

    于是父子进程间用于双向通信的所有基础设施都已经准备好了。

    总结下,Nodejs通过pipe实现IPC,主要包括以下几个步骤:

    • 主进程在fork产生子进程前生成一个pipe占位符,提示后续会有pipe创建。
    • 通过系统的socketpair把双工通道绑定到此pipe占位符上。
    • 通过环境变量把文件描述符fd传给子进程。
    • 子进程通过fd创建pipe,此pipe替代占位符进行通信。

    例子:

    // master
    const WriteWrap = process.binding('stream_wrap').WriteWrap;
    var cp = require('child_process');
    
    var worker = cp.fork(__dirname + '/ipc_worker.js');
    var channel = worker._channel;
    
    channel.onread = function (len, buf, handle) {
        if (buf) {
            console.log(buf.toString())
            channel.close()
        } else {
            channel.close()
            console.log('channel closed');
        }
    }
    
    var message = { hello: 'worker',  pid: process.pid };
    var req = new WriteWrap();
    var string = JSON.stringify(message) + '\n';
    channel.writeUtf8String(req, string, null);
    
    // worker
    const WriteWrap = process.binding('stream_wrap').WriteWrap;
    const channel = process._channel;
    
    channel.ref();
    channel.onread = function (len, buf, handle) {
        if (buf) {
            console.log(buf.toString())
        }else{
            process._channel.close()
            console.log('channel closed');
        }
    }
    
    var message = { hello: 'master',  pid: process.pid };
    var req = new WriteWrap();
    var string = JSON.stringify(message) + '\n';
    channel.writeUtf8String(req, string, null);
    
    进程失联

    进程失联是在子进程退出前通知主进程,主进程fork一个新的子进程,然后原来的子进程退出;主进程通过是子进程的disconnect事件监听其状态。
    例子:

    const WriteWrap = process.binding('stream_wrap').WriteWrap;
    const net = require('net');
    const fork = require('child_process').fork;
    
    var workers = [];
    for (var i = 0; i < 4; i++) {
         var worker = fork(__dirname + '/multi_worker.js');
         worker.on('disconnect', function () {
             console.log('[%s] worker %s is disconnected', process.pid, worker.pid);
         });
         workers.push(worker);
    }
    
    var handle = net._createServerHandle('0.0.0.0', 3000);
    handle.listen();
    handle.onconnection = function (err,handle) {
        var worker = workers.pop();
        var channel = worker._channel;
        var req = new WriteWrap();
        channel.writeUtf8String(req, 'dispatch handle', handle);
        workers.unshift(worker);
    }
    
    const net = require('net');
    const WriteWrap = process.binding('stream_wrap').WriteWrap;
    const channel = process._channel;
    var buf = 'hello Node.js';
    var res = ['HTTP/1.1 200 OK','content-length:' + buf.length].join('\r\n') + '\r\n\r\n' + buf;
    
    channel.ref(); //防止进程退出
    channel.onread = function (len, buf, handle) {
        console.log('[%s] worker %s got a connection', process.pid, process.pid);
        var socket = new net.Socket({
            handle: handle
        });
        socket.readable = socket.writable = true;
        socket.end(res);
        console.log('[%s] worker %s is going to disconnect', process.pid, process.pid);
        channel.close();
    }
    
    参考文章

    当我们谈论 cluster 时我们在谈论什么(上)
    当我们谈论 cluster 时我们在谈论什么(下)

    多进程单线程模型与单进程多线程模型之争
    多进程和多线程的优缺点
    Node.js的线程和进程

    相关文章

      网友评论

        本文标题:js的单线程和多进程

        本文链接:https://www.haomeiwen.com/subject/dswwyttx.html