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
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也是非阻塞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有gil影響了他的多cpu利用。
GIL是CPython中特有的全局解釋器鎖
這把鎖在解釋器進程中是全局有效的,它主要鎖定Python線程的CPU執行資源。
想要執行多核的進程須要知足2個條件
python在單核cpu上執行沒有問題,這個線程總能得到gil,可是在多核的時候,線程會出現競爭,GIL只能同時被一個線程申請到,沒申請到的就會被阻塞,就會一直處於閒置狀態。
到線程切換時間而後睡眠,被喚醒以後獲取gil又失敗,惡性循環。
特別是計算型線程,會一直持有gil。
GIL 能夠被 C 擴展釋放,Python 標準庫會在每次 I/O 阻塞結束後釋放 GIL,所以 GIL 不會對 I/O 服務器產生很大的性能影響。所以你能夠 fork 進程或者建立多線程來建立網絡服務器處理異步 I/O,GIL 在這種狀況下並無影響。
解決方案:
Python 3.2開始使用新的GIL。新的GIL實現中用一個固定的超時時間來指示當前的線程放棄全局鎖。在當前線程保持這個鎖,且其餘線程請求這個鎖時,當前線程就會在5毫秒後被強制釋放該鎖。
node是沒有多線程的利用的,只能用多進程來利用多核cpu,python由於gil的問題,也無法徹底利用多線程,可是有一些神奇的方案能夠利用好比指定cpu運行。 go的實現是比較好的,畢竟是後來的語言,能夠多核跑協程,來利用cpu