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

    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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM