go/node/python 多進程與多核cpu

node

node單線程,沒有併發,可是能夠利用cluster進行多cpu的利用。cluster是基於child_process的封裝,幫你作了建立子進程,負載均衡,IPC的封裝。javascript

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\n');

    process.send({ cmd: 'notifyRequest' });
  }).listen(8000);
}

咱們經過cluster.fork()來創造幾個子進程,讓子進程來替咱們工做。在fork的時候會傳一個參數到子進程,cluster.isMaster就是根據有沒有這個參數判斷的。
若是是子進程就起一個server。
每一個子進程都會綁定到8000端口,這不會引發端口占用嗎?
答案是不會,由於listen並不會真的監聽到8000端口,它會經過IPC把子進程的消息傳到主進程,主進程會建立服務器,而後調用子進程的回調。
在子進程的回調中:子進程會根據主進程是否返回handle句柄來執行下一步的操做,若是沒有handle句柄,說明在負載均衡的策略沒有選中本進程。那麼就本身造一個handle對象返回。
那本身造個對象怎麼返回請求呢?
請求到主進程,主進程會分發請求,處理到該請求的子進程會經過IPC與主進程通訊,這樣就完成了一個請求的響應。java

經過cluster完成單機器的負載均衡,那麼多機器呢?仍是得用nginx。node

node & pm2

pm2 是node的進程管理工具,它封裝了cluster,能夠經過命令行來建立多個進程來處理。python

寫個config文件:
app.jsonnginx

{
    "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。golang

經過參數開啓多個子進程,而不須要修改咱們的業務代碼。json

go

go也是非阻塞io,Golang默認全部的任務都在一個cpu核裏,若是想使用多核來跑goroutine的任務,須要配置runtime.GOMAXPROCS。
自從Go 1.5開始, Go的GOMAXPROCS默認值已經設置爲 CPU的核數,咱們不用手動設置這個參數。
咱們先說說go的併發。
go自己就能夠經過go關鍵字來進行併發操做。go關鍵字建立的併發單元在go中叫goroutine。
好比:bash

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

相關文章
相關標籤/搜索