正如动静是相对的概念,有了它们,世界才充满盎然生气;变和不变也是哲学上的对立统一,在代码的世界里也一样;同步异步呢?在这一篇文字里面已经很粗略地提到了同步和异步各自有些什么好处,接下来,我不妨说一些同步和异步互相转化的故事。
先来看看这一段代码:
setTimeout(function(){ while(true){ alert("In"); } },0); while(true){ alert("Out"); }
它的输出应该是怎样的呢?你可以试一试。
不同浏览器下它的表现不同,有的浏览器下一直显示In的弹出框;有的浏览器下一直显示Out的弹出框;还有的浏览器下先显示一个Out,再不断显示In的弹出框。
那是不是可以这样理解:
上面的代码本意是想描述一个页面的JavaScript代码进行类似于并行线程的执行(setTimeout调用的方法,似乎就是一个异步执行的方法,它本意是不阻止主流程的执行的),可是实际运行的结果发现,原来浏览器运行JavaScript,所谓的异步,只是对开发人员和用户的一个欺骗,世界只是看起来这个样子——实际上,在JavaScript的世界里,其实根本就是“单线程”的嘛!
其实,这是无所谓欺骗的,就如同对于单CPU的机器来说,所谓的多进程,其实也只有一个CPU轮流执行队列里的指令。只是这个世界本来就是那么残酷,也许是我们都看错了……
同步Ajax和异步Ajax
Ajax通常都是异步的,同步的Ajax调用会将浏览器当前页面挂起,拒绝一切用户操作,直至响应到达:
var req = new XMLHttpRequest(); req.open("GET", url, true); //true表示异步,false表示同步 req.onreadystatechange = callback; req.send();
JavaScript的一个悲剧
在JavaScript中,没有一个方法可以让主线程休息一段时间(Java中有sleep和wait),也就是说,如果我想在某一个执行逻辑中,休息一会、等待一会,这样的实现都会变得很困难(Jscex就是用来解决这样的问题的)。这似乎是JavaScript的一个天生的重大缺陷。
Jscex带来的最大好处,就在于可以用同步的思维和编码,来解决异步的问题:
var moveAsync = eval(Jscex.compile("$async", function(e, startPos, endPos, duration) { for (var t = 0; t < duration; t += 50) { e.style.left = startPos.x + (endPos.x - startPos.x) * (t / duration); e.style.top = startPos.y + (endPos.y - startPos.y) * (t / duration); $await(Jscex.Async.sleep(50)); } e.style.left = endPos.x; e.style.top = endPos.y; }));
Barrier模式
Barrier是一道篱笆,所有的不同异步线程,都先先后后运行完毕以后(都撞到了篱笆上),再汇成一束主流程继续往后走:
JDK的CyclicBarrier类就是用来实现这个模式的(A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.):
class Solver {
final int N;
final float[][] data;
final CyclicBarrier barrier;
class Worker implements Runnable {
int myRow;
Worker(int row) {
myRow = row;
}
public void run() {
while (!done()) {
processRow(myRow); //执行某一行的逻辑
try {
barrier.await(); //该行逻辑执行完毕后,告知一下
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
}
public Solver(float[][] matrix) {
data = matrix;
N = matrix.length;
barrier = new CyclicBarrier(N,
new Runnable() {
public void run() {
mergeRows(...); //在每一行都处理完毕以后,执行一个merge操作
}
});
for (int i = 0; i < N; ++i)
new Thread(new Worker(i)).start();
waitUntilDone();
}
}
而在JavaScript中,也可以实现类似的效果:
var count = 3; for(var i=0; i<=count; i++){ setTimeout(function(){ doXXX(); // 执行任务 count --; //每个子任务执行完毕后都标记一下 if(!count) doFinalXXX(); //Barrier的汇总任务 },0); }
如果有了Jscex,实现可以更简洁:
function (taskA, taskB, taskC) { $await(Jscex.Async.parallel(taskA, taskB)); //先并行执行任务A、B $await(taskC); //在A、B都完成后再执行C }
Future和Promise
Future、Promise是用于并发编程的一种同步构造。它们表示一个对象,这个对象用来作为一次计算的结果的代理,而该结果被初始化为未知,因为这个对象产生时计算还没有结束(或还没有开始)。
Java中有Future可以帮助实现:
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Object> task = new Callable<Object>() {
public Object call() throws Exception {
doXXX();
return result;
}
};
Future<Object> future = executor.submit(task);
boolean isCancelled = future.isCancelled(); //查询状态,调用cancel方法可以取消任务
boolean isDone = future.isDone(); //查询状态
Object res = future.get(); // 等待至完成
JavaScript可以实现成类似这样子:
Promise.when(promise1, promise2).then(function (data1, data2) {...});
具体请参见这里。
文章系本人原创,转载请注明出处和作者