正如动静是相对的概念,有了它们,世界才充满盎然生气;变和不变也是哲学上的对立统一,在代码的世界里也一样;同步异步呢?
首先,来粗略地看看同步和异步各自有些什么好处:
同步的好处:
- 1、同步流程对结果处理通常更为简单,可以就近处理。
- 2、同步流程对结果的处理始终和前文保持在一个上下文内。
- 3、同步流程可以很容易捕获、处理异常。
- 4、同步流程是最天然的控制过程顺序执行的方式。
异步的好处:
- 1、异步流程可以立即给调用方返回初步的结果。
- 2、异步流程可以延迟给调用方最终的结果数据,在此期间可以做更多额外的工作,例如结果记录等等。
- 3、异步流程在执行的过程中,可以释放占用的线程等资源,避免阻塞,等到结果产生再重新获取线程处理。
- 4、异步流程可以等多次调用的结果出来后,再统一返回一次结果集合,提高响应效率。
接下来,我不妨说一些同步和异步互相转化的故事。
先来看看这一段代码:
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) {...});
具体请参见这里。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》