python 多进程 多核_go/node/python 多进程与多核cpu
node
node單線程,沒有并發(fā),但是可以利用cluster進(jìn)行多cpu的利用。cluster是基于child_process的封裝,幫你做了創(chuàng)建子進(jìn)程,負(fù)載均衡,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()來創(chuàng)造幾個(gè)子進(jìn)程,讓子進(jìn)程來替我們工作。在fork的時(shí)候會(huì)傳一個(gè)參數(shù)到子進(jìn)程,cluster.isMaster就是根據(jù)有沒有這個(gè)參數(shù)判斷的。
如果是子進(jìn)程就起一個(gè)server。
每個(gè)子進(jìn)程都會(huì)綁定到8000端口,這不會(huì)引起端口占用嗎?
答案是不會(huì),因?yàn)閘isten并不會(huì)真的監(jiān)聽到8000端口,它會(huì)通過IPC把子進(jìn)程的消息傳到主進(jìn)程,主進(jìn)程會(huì)創(chuàng)建服務(wù)器,然后調(diào)用子進(jìn)程的回調(diào)。
在子進(jìn)程的回調(diào)中:子進(jìn)程會(huì)根據(jù)主進(jìn)程是否返回handle句柄來執(zhí)行下一步的操作,如果沒有handle句柄,說明在負(fù)載均衡的策略沒有選中本進(jìn)程。那么就自己造一個(gè)handle對(duì)象返回。
那自己造個(gè)對(duì)象怎么返回請(qǐng)求呢?
請(qǐng)求到主進(jìn)程,主進(jìn)程會(huì)分發(fā)請(qǐng)求,處理到該請(qǐng)求的子進(jìn)程會(huì)通過IPC與主進(jìn)程通信,這樣就完成了一個(gè)請(qǐng)求的響應(yīng)。
通過cluster完成單機(jī)器的負(fù)載均衡,那么多機(jī)器呢?還是得用nginx。
node & pm2
pm2 是node的進(jìn)程管理工具,它封裝了cluster,可以通過命令行來創(chuàng)建多個(gè)進(jìn)程來處理。
寫個(gè)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個(gè)instance。
通過參數(shù)開啟多個(gè)子進(jìn)程,而不需要修改我們的業(yè)務(wù)代碼。
go
go也是非阻塞io,Golang默認(rèn)所有的任務(wù)都在一個(gè)cpu核里,如果想使用多核來跑goroutine的任務(wù),需要配置runtime.GOMAXPROCS。
自從Go 1.5開始, Go的GOMAXPROCS默認(rèn)值已經(jīng)設(shè)置為 CPU的核數(shù),我們不用手動(dòng)設(shè)置這個(gè)參數(shù)。
我們先說說go的并發(fā)。
go本身就可以通過go關(guān)鍵字來進(jìn)行并發(fā)操作。go關(guān)鍵字創(chuàng)建的并發(fā)單元在go中叫g(shù)oroutine。
比如:
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)
}
會(huì)打印789 ,123,456,或者 780,456,123。
在主線程開始就通過go字段開啟了2個(gè)goroutine,兩個(gè)goroutine的執(zhí)行順序不確定。
如果當(dāng)前goroutine發(fā)生阻塞,它就會(huì)讓出CPU給其他goroutine。
如果當(dāng)前goroutine不發(fā)生阻塞,一直在執(zhí)行,那么什么時(shí)候執(zhí)行其他goroutine就看go調(diào)度器的處理了。
不過go提供runtime.Gosched()來達(dá)到讓出CPU資源效果的函數(shù),當(dāng)然不是不執(zhí)行,會(huì)在之后的某個(gè)時(shí)間段執(zhí)行。如果把注釋去掉,789就會(huì)最后執(zhí)行。
單核的時(shí)候其實(shí)goroutine并不是真的“并行”,goroutine都在一個(gè)線程里,它們之間通過不停的讓出時(shí)間片輪流運(yùn)行,達(dá)到類似并行的效果。
如果我在123,或者456之前加 time.Sleep(time.Second)。那么CPU的資源又會(huì)轉(zhuǎn)讓回主進(jìn)程。
當(dāng)一個(gè)goroutine發(fā)生阻塞,Go會(huì)自動(dòng)地把與該goroutine處于同一系統(tǒng)線程的其他goroutines轉(zhuǎn)移到另一個(gè)系統(tǒng)線程上去,以使這些goroutines不阻塞,主線程返回的時(shí)候goroutines又進(jìn)入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
}
}
會(huì)打印什么呢?
runtime.GOMAXPROCS(2)改成雙核cpu,又會(huì)打印什么呢?
我們能看到,雙核cpu的時(shí)候,goroutine會(huì)真正的并發(fā)執(zhí)行而不是并行。他們會(huì)搶占式的執(zhí)行。
參考https://studygolang.com/articles/1661
python
python是有多線程的,但是python有g(shù)il影響了他的多cpu利用。
GIL是CPython中特有的全局解釋器鎖
這把鎖在解釋器進(jìn)程中是全局有效的,它主要鎖定Python線程的CPU執(zhí)行資源。
想要執(zhí)行多核的進(jìn)程需要滿足2個(gè)條件
被操作系統(tǒng)調(diào)度出來【操作系統(tǒng)允許它占用CPU】
獲取到GIL【CPython解釋器允許它執(zhí)行指令】
python在單核cpu上執(zhí)行沒有問題,這個(gè)線程總能獲得gil,但是在多核的時(shí)候,線程會(huì)出現(xiàn)競(jìng)爭(zhēng),GIL只能同時(shí)被一個(gè)線程申請(qǐng)到,沒申請(qǐng)到的就會(huì)被阻塞,就會(huì)一直處于閑置狀態(tài)。
到線程切換時(shí)間然后睡眠,被喚醒之后獲取gil又失敗,惡性循環(huán)。
特別是計(jì)算型線程,會(huì)一直持有g(shù)il。
GIL 可以被 C 擴(kuò)展釋放,Python 標(biāo)準(zhǔn)庫會(huì)在每次 I/O 阻塞結(jié)束后釋放 GIL,因此 GIL 不會(huì)對(duì) I/O 服務(wù)器產(chǎn)生很大的性能影響。因此你可以 fork 進(jìn)程或者創(chuàng)建多線程來創(chuàng)建網(wǎng)絡(luò)服務(wù)器處理異步 I/O,GIL 在這種情況下并沒有影響。
解決方案:
使用python3.4或更高版本(對(duì)GIL機(jī)制進(jìn)行了優(yōu)化)
使用多進(jìn)程替換多線程(多進(jìn)程之間沒有GIL,但是進(jìn)程本身的資源消耗較多)
指定cpu運(yùn)行線程(使用affinity模塊)
全I(xiàn)O密集型任務(wù)時(shí)使用多線程
協(xié)程 (gevent模塊)
Python 3.2開始使用新的GIL。新的GIL實(shí)現(xiàn)中用一個(gè)固定的超時(shí)時(shí)間來指示當(dāng)前的線程放棄全局鎖。在當(dāng)前線程保持這個(gè)鎖,且其他線程請(qǐng)求這個(gè)鎖時(shí),當(dāng)前線程就會(huì)在5毫秒后被強(qiáng)制釋放該鎖。
總結(jié)
node是沒有多線程的利用的,只能用多進(jìn)程來利用多核cpu,python因?yàn)間il的問題,也沒法完全利用多線程,但是有一些神奇的方案可以利用比如指定cpu運(yùn)行。
go的實(shí)現(xiàn)是比較好的,畢竟是后來的語言,可以多核跑協(xié)程,來利用cpu
總結(jié)
以上是生活随笔為你收集整理的python 多进程 多核_go/node/python 多进程与多核cpu的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java字符转为数字_Java 判断字符
- 下一篇: python面试设计模式问题_聊聊 Py