我们都知道nodejs最大的特点就是单进程、无阻塞运行,并且是异步事件驱动的。Nodejs的这些特性能够很好的解决一些问题,例如在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。既然Node.js采用单进程、单线程模式,那么在如今多核硬件流行的环境中,单核性能出色的Nodejs如何利用多核CPU呢?创始人Ryan Dahl建议,运行多个Nodejs进程,利用某些通信机制来协调各项任务。目前,已经有不少第三方的Node.js多进程支持模块发布,而NodeJS 0.6.x 以上的版本提供了一个cluster模块 ,允许创建“共享同一个socket”的一组进程,用来分担负载压力。
本篇文章就基于该cluster模块来讲述Node.js在多核CPU下的编程。
Cluster模块介绍
nodejs所提供的cluster模块目前尚处于试验阶段,在v0.10.7的官方文档上我们可以看到模块的发布信息如下:
Stability: 1 - Experimental
关于该模块的功能,源文档描述如此“A single instance of Node runs in a single thread. To take advantage of multi-core systems the user will sometimes want to launch a cluster of Node processes to handle the load.” 其意就是:Node的示例以单进程的模式运行,有时为了充分利用多核系统的资源用户需要运行一组Node进程来分担负载。
Cluster用法介绍
首先贴出一段该模块示例应用代码,接下来进行详细分析,代码如下:
var cluster = require("cluster");
var http = require("http");
var numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
require("os").cpus().forEach(function(){
cluster.fork();
});
cluster.on("exit", function(worker, code, signal) {
console.log("worker " + worker.process.pid + " died");
});
cluster.on("listening", function(worker, address) {
console.log("A worker with #"+worker.id+" is now connected to " +
address.address +
":" + address.port);
});
} else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
console.log("Worker #" + cluster.worker.id + " make a response");
}).listen(8000);
}
这段代码很简单,主线程就是当前运行的js文件,主线程根据你本机系统的核数来创建子进程。所有进程共享一个监听端口8000,当有请求发起时,主线程会将该请求随机分配给某个子进程。console.log("Worker #" + cluster.worker.id + " make a response");这句代码可以打印出是哪个进程处理该请求。
问题分析
我们前面提到有请求发起时,由系统来决定将该请求交给哪个进程进行处理。这种完全依赖于系统的负载均衡存在着一个重要缺陷:在windows,linux和Solaris上,只要某个子进程的accept queue为空(通常为最后创建的那个子进程),系统就会将多个connetion分配到同一个子进程上,这会造成进程间负载极为不均衡。特别是在使用长连接的时候,单位时间内的new coming connection并不高,子进程的accept queue往往均为空,就会导致connection会不停的分配给同一个进程。所以这种负载均衡完全依赖于accept queue的空闲程度,只有在使用短连接,而且并发非常高的情况下,才能达到负载均衡,但是这个时候系统的load会非常高,系统也会变得不稳定起来。
Java 中Object的wait() notify() notifyAll()方法使用
一、前言
对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多。
二、线程安全基本知识
首先应该记住以下基本点,先背下来也无妨:
同一时间一个锁只能被一个线程持有 调用对象的wait()和notify()前必须持有它
三、wait()和notify()理解
3.1 wait()和notify()方法简介
wait()和notify()都是Object的方法,可以认为任意一个Object都是一种资源(或者资源的一个代表),当多个线程对一个资源进行操作时,如果线程发现这个资源还没有准备好,它就可以在这个资源上进行等待,即调用这个资源的wait()方法,如果有另外的线程经过某些处理觉得这个资源可用了,会调用这个这个资源的notify()方法,告诉等待它的线程,这个资源可以用了。
当然不使用wait()和notify()方法也是可以的,可以用while()死循环来判断,如下面的伪代码:
class Resource{
static boolean canUse=false;
}
while(!Resource.canUse){
//如果不可用,死循环在这里等待
}
//当资源可以使用后,就会跳出循环,往下执行
这样做是可以,但是特别消耗CPU资源,所以建议用户使用wait()和notify()方法。
3.2 wait()和notify()的价值
其实从单词意思来看就能看出来,wait就是等待,说明这个资源没有准备好,我要等,还有这一个wait(long timeout) ,表示我只能等待你这么长时间了,过时不候啊,而调用notify()的线程肯定就是对资源进行处理的,处理完进行通知。所以呢,它们就经常用在生产者和消费者模式中。任何涉及等资源到来的情景都适合用这两个方法,
3.3 为什么wait()和notify()必须和synchronized一起使用
当不在synchronized同步块中使用wait()和notify()或者调用方法的对象不是synchronized的同步锁就会抛异常:
java.lang.IllegalMonitorStateException
很多人会疑惑为什么必须持有这个对象的锁才能调用对象的wait()和notify()方法呢,我也有这个疑惑,而且我认为这么做是没有必要的。首先看下面的代码:
public class WaitTest{
// 这是一个资源,模拟的Object
final NoObjct resource=new NoObjct();
public static void main(String[] args) throws Exception{
WaitTest d=new WaitTest();
d.test();
}
public void test() throws Exception{
Runnable r=new Runnable(){
public void run(){
// 调用资源的模拟的wait方法,在方法内部使用synchronized
resource.noWait();
System.out.println("线程等待完,执行");
}
};
Thread t=new Thread(r);
t.start();
Thread.sleep(2000);
System.out.println("准备唤醒等待资源的线程");
// 调用资源的模拟的notify方法,在方法内部使用synchronized
resource.noNotify();
}
}
// 因wait()和notify()是final方法,不能覆盖,所以模拟一个Object对象
class NoObjct{
// 模拟wait方法
public void noWait(){
// 这个就相当于将synchronized放到wait方法内部
synchronized(this){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
// 模拟notify方法
public void noNotify(){
// 这个就相当于将synchronized放到notify方法内部
synchronized(this){
this.notify();
}
}
}
这是一个简单的wait()和notify()例子,wait等待,notify唤醒。如果忽略掉模拟的Object会发现代码简洁了许多,否则就要每次使用synchronized,如下代码:
public class WaitTest{
// 这是一个资源,模拟的Object
final Object resource=new Object();
public static void main(String[] args) throws Exception{
WaitTest d=new WaitTest();
d.test();
}
public void test() throws Exception{
Runnable r=new Runnable(){
public void run(){
// 必须使用synchronized
try{
synchronized(resource){
resource.wait();
}
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("线程等待完,执行");
}
};
Thread t=new Thread(r);
t.start();
Thread.sleep(2000);
System.out.println("准备唤醒等待资源的线程");
// 必须使用synchronized
synchronized(resource){
resource.notify();
}
}
}
所以呢,我觉得wait()和notify()和synchronized一起没有什么意义,毕竟synchronized用来进行代码同步的,和线程之间唤醒没有什么关系(希望有读者能给我相反的意见并说服我)。但是既然这么规定了就必须要去遵守,即必须在synchronized中使用wait和notify,且调用方法的对象必须是同步对象。
四、何时使用wait()和notify()
在上面已经说了这两个方法的其中一个价值就是用在生产者和消费者模式。但是通过使用它们来构建的生产者和消费者模型很低级而且复杂,完全可以使用BlockingQueue接口的实现类来构建。比如可以使用ArrayBlockingQueue,它既能保证线程安全又能实现阻塞效果,何乐而不为呢。
除此之外就只有线程间休眠与唤醒了。