nodejs学习笔记(二)——javascript的同步异步行为和多线程

写过后台的同学一定对线程、线程池或者是多线程这些概念不会陌生,但是前台在HTML5之前很少会提及,因为在HTML5之前javascript都是单线程的。下面用一个简单的例子来说明一下单线程:
setInterval(function(){
    var date = new Date();
        console.log(date);
},100)

for(var i=0;i<10000000000;i++){
    if(i%1000000000 == 0){
        var date = new Date();
        console.log("for"+date);
    }
}

首先启动一个100毫秒调用一次的定时器,打印当前时间,然后再执行一个for循环,在循环里面打印当前时间,为了避免打印过多,在循环里面只有在可以整除的情况下才会打印。for循环遍历的值很大,所以整个for循环执行必然会超过100毫秒,但由于javascript的单线程特性,定时器的打印行为即使过了100毫秒也不会执行。

把上例代码保存在test2.js中,然后通过node test2.js命令来验证(可以看到for循环执行了将近一分钟才开始执行定时器的打印):

单线程会带来一个显而易见的问题,如一个函数向后台服务发送请求,而服务器可能由于各种各样的原因未响应(比如网络不通、server端IO很高或者更低级一点:server端出错了但是未进行错误处理),这样就会阻塞后续代码的执行(比如后续有渲染页面的代码,阻塞就会导致页面显示不完整),同时在等待的过程中页面会被锁定,影响用户体验。下面就给一个这样的例子:
后台java代码:
package test;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class test3
 */
@WebServlet("/test3")
public class test3 extends HttpServlet {
	private static final long serialVersionUID = 1L;

    /**
     * Default constructor. 
     */
    public test3() {
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		response.setContentType("application/json; charset=utf-8");
		PrintWriter writer = response.getWriter();
		writer.write("{\"name\":\"it is name\"}");
		writer.flush();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

}
在server端起一个简单的servlet,当有请求时,等待5秒(模拟后台无法立即响应的情况,方便页面观察)后返回一个json对象(key为name,value为it is name)。

前台代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="lib/jquery-3.0.0.min.js"></script>
</head>
<body>
<input type="text" id="input1" value="">
</body>
</html>
<script>
$.ajax({
    type: "GET",
    url: "test3",
    dataType: "json",
    async: false,
    success: function(data){
        console.log(data.name);
    }
});
console.log("end");
</script>
页面显示一个输入框可供用户输入(用于验证请求阻塞时页面状态),页面初始就会向后台发起请求,在请求完成后会在控制台先打印返回json对象的值,再打印end。

访问页面,在后台未响应时(前5秒),页面不响应用户的任何操作,在本例中表现为输入框无法输入信息,控制台无信息打印,这表示打印end已经被阻塞:

等待5秒后,页面回复正常,可以正常在输入框输入信息,控制台也打印了请求的信息和end信息:

工程跑在Eclipse Java EE IDE for Web Developers.Version: Luna Service Release 2 (4.4.2) Build id: 20150219-0600下,server中间件用的是tomcat7.0,如果在本地想要运行本例,还需要下载jquery-3.0.0.min.js(前台依赖)和javax.servlet-3.0.jar(后台依赖),以下是工程的目录结构:


上例的阻塞状态显然用户是无法接受的(比如淘宝购物时查询某一个商品详细信息时卡住了,整个购物过程就被阻塞了,是不是感觉要起飞?),不过幸运的是这个问题在上古时期就有方法解决了,就是用例子中的ajax,即异步请求(上例中的阻塞是故意设置了async: false,改成了同步请求)。

把前台代码部分的async: false,这一句删除再访问页面,此时发起请求时,页面的操作并没有被阻塞,后续的打印end也没有被阻塞(先于请求响应消息打印,即打印end先执行了):


当然,单线程的阻塞并不是仅仅体现在与后端的交互上,还包括前台自己的处理逻辑,下面看一下runoob回调函数章节的例子(同步读取文件):
var fs = require("fs");

var data = fs.readFileSync('input.txt');

console.log(data.toString());
console.log("end");

把上例代码保存在test3.js中,然后通过node test3.js命令来验证(同步读取文件,文件内容会先于end打印):


再看一下异步读取文件例子:
var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("end");

把代码保存在test3.js中,然后通过node test3.js命令来验证(异步读取文件,文件内容会后于end打印):


我们可以根据实际业务场景的需要来选择使用同步还是异步,但不论哪种请求,实际上仍未摆脱单线程的束缚,而HTML5则提供了真正意义的多线程——worker。下面是一个从w3school拿来的worker的例子:
html页面:
<!DOCTYPE html>
<html>
<body>

<p>Count numbers: <output id="result"></output></p>
<button οnclick="startWorker()">Start Worker</button>
<button οnclick="stopWorker()">Stop Worker</button>
<br /><br />

<script>
var w;

function startWorker() {
    if (typeof(Worker)!=="undefined") {
        if (typeof(w)=="undefined") {
            w=new Worker("js/test4.js");
        }
        w.onmessage = function (event) {
            document.getElementById("result").innerHTML=event.data;
        };
    } else {
        document.getElementById("result").innerHTML="Sorry, your browser does not support Web Workers...";
    }
}

function stopWorker() {
    w.terminate();
}
</script>

</body>
</html>

需要另起线程执行的javascript(需要new出Worker对象的代码,对应上面的test4.js):
var i=0;

function timedCount(){
    i=i+1;
    postMessage(i);
    setTimeout("timedCount()",500);
}

timedCount();

上例的功能为在页面上提供启动worker和停止worker的功能,worker会每隔500毫秒返回执行的结果,由启动worker的javascript来处理结果(本例为在页面上直接显示)。worker的运行与页面的操作不会互相影响,即使我们把定时器的时间间隔调整为10毫秒,页面也不会发生阻塞,以下为执行结果:


worker的数据提交(启动线程向主线程返回数据):postMessage(obj)
worker的数据接收(主线程接收启动线程返回数据):worker.onMessage = function(event)(//传递的obj数据可以通过event.data获取),这里的worker是new出来的Worker对象。

注意:本例的代码必须运行在一个web应用中,直接在浏览器中访问静态页面会报错(点击启动worker时):


此外,本例的代码也不可以在nodejs中运行,nodejs无法识别Worker,且nodejs有自己的多线程库cluster。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值