zoukankan      html  css  js  c++  java
  • go/node/python 多进程与多核cpu

    node

    node单线程,没有并发,但是可以利用cluster进行多cpu的利用。cluster是基于child_process的封装,帮你做了创建子进程,负载均衡,IPC的封装。

    const cluster = require('cluster');
    const http = require('http');
    
    if (cluster.isMaster) {
    
      let numReqs = 0;
      setInterval(() => {
        console.log(`numReqs = ${numReqs}`);
      }, 1000);
    
      function messageHandler(msg) {
        if (msg.cmd && msg.cmd === 'notifyRequest') {
          numReqs += 1;
        }
      }
    
      const numCPUs = require('os').cpus().length;
      for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
      }
    
      for (const id in cluster.workers) {
        cluster.workers[id].on('message', messageHandler);
      }
    
    } else {
    
      // Worker processes have a http server.
      http.Server((req, res) => {
        res.writeHead(200);
        res.end('hello world
    ');
    
        process.send({ cmd: 'notifyRequest' });
      }).listen(8000);
    }
    

    我们通过cluster.fork()来创造几个子进程,让子进程来替我们工作。在fork的时候会传一个参数到子进程,cluster.isMaster就是根据有没有这个参数判断的。
    如果是子进程就起一个server。
    每个子进程都会绑定到8000端口,这不会引起端口占用吗?
    答案是不会,因为listen并不会真的监听到8000端口,它会通过IPC把子进程的消息传到主进程,主进程会创建服务器,然后调用子进程的回调。
    在子进程的回调中:子进程会根据主进程是否返回handle句柄来执行下一步的操作,如果没有handle句柄,说明在负载均衡的策略没有选中本进程。那么就自己造一个handle对象返回。
    那自己造个对象怎么返回请求呢?
    请求到主进程,主进程会分发请求,处理到该请求的子进程会通过IPC与主进程通信,这样就完成了一个请求的响应。

    通过cluster完成单机器的负载均衡,那么多机器呢?还是得用nginx。

    node & pm2

    pm2 是node的进程管理工具,它封装了cluster,可以通过命令行来创建多个进程来处理。

    写个config文件:
    app.json

    {
        "name"        : "app",
        "script"      : "src/main.js",
        "watch"       : true,
        "merge_logs"  : true,
        "instances"   : "max", // 使用cluster
        "error_file" : "./log/error.log",
        "out_file"   : "./log/asccess.log",
        "pid_file"   : "./log/pid.pid",
        "cwd"         : "./",
        "max_restarts" : 10, 
        "min_uptime": "10s",
        "env": {
            "NODE_ENV": "development",
            "BABEL_ENV": "node"
        },
        "env_prod" : {
            "NODE_ENV": "production"
        }
    }
    
    pm2 start app.json
    

    也可以不写配置文件直接写pm2 start -i 4 --name server index.js
    开启4个instance。

    通过参数开启多个子进程,而不需要修改我们的业务代码。

    go

    go也是非阻塞io,Golang默认所有的任务都在一个cpu核里,如果想使用多核来跑goroutine的任务,需要配置runtime.GOMAXPROCS。
    自从Go 1.5开始, Go的GOMAXPROCS默认值已经设置为 CPU的核数,我们不用手动设置这个参数。
    我们先说说go的并发。
    go本身就可以通过go关键字来进行并发操作。go关键字创建的并发单元在go中叫goroutine。
    比如:

     package main
     
      import (
          "fmt"
          "time",
       //   "runtime" 
      )
    func main() {
        go func(){
            fmt.Println("123")
        }()
    
         go func(){
            fmt.Println("456")
        }()
       // runtime.Gosched()
        fmt.Println("789")
        time.Sleep(time.Second)
    }
    

    会打印789 ,123,456,或者 780,456,123。
    在主线程开始就通过go字段开启了2个goroutine,两个goroutine的执行顺序不确定。
    如果当前goroutine发生阻塞,它就会让出CPU给其他goroutine。
    如果当前goroutine不发生阻塞,一直在执行,那么什么时候执行其他goroutine就看go调度器的处理了。

    不过go提供runtime.Gosched()来达到让出CPU资源效果的函数,当然不是不执行,会在之后的某个时间段执行。如果把注释去掉,789就会最后执行。

    单核的时候其实goroutine并不是真的“并行”,goroutine都在一个线程里,它们之间通过不停的让出时间片轮流运行,达到类似并行的效果。
    如果我在123,或者456之前加 time.Sleep(time.Second)。那么CPU的资源又会转让回主进程。

    当一个goroutine发生阻塞,Go会自动地把与该goroutine处于同一系统线程的其他goroutines转移到另一个系统线程上去,以使这些goroutines不阻塞,主线程返回的时候goroutines又进入runqueue

    下面这段代码:

    
    import (
        "fmt"
        "runtime"
    )
    
    var quit chan int = make(chan int)
    
    func loop() {
        for i := 0; i < 100; i++ { //为了观察,跑多些
            fmt.Printf("%d ", i)
        }
        quit <- 0
    }
    
    func main() {
        runtime.GOMAXPROCS(1)
    
        go loop()
        go loop()
    
        for i := 0; i < 2; i++ {
            <-quit
        }
    }
    

    会打印什么呢?
    runtime.GOMAXPROCS(2)改成双核cpu,又会打印什么呢?
    我们能看到,双核cpu的时候,goroutine会真正的并发执行而不是并行。他们会抢占式的执行。

    参考https://studygolang.com/articles/1661

    python

    python是有多线程的,但是python有gil影响了他的多cpu利用。
    GIL是CPython中特有的全局解释器锁
    这把锁在解释器进程中是全局有效的,它主要锁定Python线程的CPU执行资源。
    想要执行多核的进程需要满足2个条件

    1. 被操作系统调度出来【操作系统允许它占用CPU】
    2. 获取到GIL【CPython解释器允许它执行指令】

    python在单核cpu上执行没有问题,这个线程总能获得gil,但是在多核的时候,线程会出现竞争,GIL只能同时被一个线程申请到,没申请到的就会被阻塞,就会一直处于闲置状态。
    到线程切换时间然后睡眠,被唤醒之后获取gil又失败,恶性循环。

    特别是计算型线程,会一直持有gil。

    GIL 可以被 C 扩展释放,Python 标准库会在每次 I/O 阻塞结束后释放 GIL,因此 GIL 不会对 I/O 服务器产生很大的性能影响。因此你可以 fork 进程或者创建多线程来创建网络服务器处理异步 I/O,GIL 在这种情况下并没有影响。

    解决方案:

    1. 使用python3.4或更高版本(对GIL机制进行了优化)
    2. 使用多进程替换多线程(多进程之间没有GIL,但是进程本身的资源消耗较多)
    3. 指定cpu运行线程(使用affinity模块)
    4. 全IO密集型任务时使用多线程
    5. 协程 (gevent模块)

    Python 3.2开始使用新的GIL。新的GIL实现中用一个固定的超时时间来指示当前的线程放弃全局锁。在当前线程保持这个锁,且其他线程请求这个锁时,当前线程就会在5毫秒后被强制释放该锁。

    总结

    node是没有多线程的利用的,只能用多进程来利用多核cpu,python因为gil的问题,也没法完全利用多线程,但是有一些神奇的方案可以利用比如指定cpu运行。
    go的实现是比较好的,毕竟是后来的语言,可以多核跑协程,来利用cpu

  • 相关阅读:
    js总结:增加和减少文本框
    java总结:字符串切割
    Spring-----ioc
    Hibernate之二级缓存
    Hibernate之HQL
    Hibernate关联关系(一对多自关联 ,多对多)
    Hibernate关联关系(一对多)
    Hibernate之主键生成策略
    如何使用hibernate完成CRUD操作
    Struts2-----文件上传与拦截器原理
  • 原文地址:https://www.cnblogs.com/dh-dh/p/9174294.html
Copyright © 2011-2022 走看看