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