术语“异步”和“并行”常常被混为一谈,异步是关于现在和将来的时间间隙,而并行是关于能够同时发生的事情。
并行计算最常见的工具就是进程和线程。进程和线程独立运行,并可能同时运行:在不同的处理器,甚至不同的计算机上,但多个线程能够共享单个进程的内存。与之相对的是,事件循环把自身的工作分成一个个任务并顺序执行,不允许对共享内存的 并行访问和修改。通过分立线程中彼此合作的事件循环,并行和顺序执行可以共存。
并行线程的交替执行和异步事件的交替调度,其粒度是完全不同的。
多线程编程是非常复杂的。因为如果不通过特殊的步骤来防止这种中断和交错运行的话,可能会得到出乎意料的、不确定的行为,通常这很让人头疼。
js 从不跨线程共享数据,这意味着不需要考虑这一层次的不确定性。但是这并不意味着 js 总是确定性的。
var a = 20;
function foo() {
a = a + 1;
}
function bar() {
a = a * 2;
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
foo()
和 bar()
的相对顺序改变可能会导致不同结果
完整运行
由于 js 的单线程特性,foo()
(以及 bar()
)中的代码具有原子性。也就是说,一旦 foo()
开始运行,它的所有代码都会在 bar()
中的任意代码运行之前完成,或者相反。 这称为完整运行(run-to-completion)特性。
实际上,如果 foo()
和 bar()
中的代码更长,完整运行的语义就会更加清晰,比如:
var a = 1;
var b = 2;
function foo() {
a++;
b = b * a;
a = b + 3; }
function bar() {
b--;
a = 8 + b;
b = a * 2;
}
// ajax(..)是某个库中提供的某个Ajax函数
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );
由于 foo()
不会被 bar()
中断,bar()
也不会被 foo()
中断,所以这个程序只有两个可能的输出,取决于这两个函数哪个先运行——如果存在多线程,且 foo()
和 bar()
中的语句可 以交替运行的话,可能输出的数目将会增加不少!
在 js 的特性中,这种函数顺序的不确定性就是通常所说的竞态条件(race condition),foo()
和 bar()
相互竞争,看谁先运行。具体来说,因为无法可靠预测 a
和 b
的最终结果,所以才是竞态条件。