【譯】異步:現(xiàn)在與將來 (并行篇)

原文: https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch1.md#parallel-threading
譯者:熊賢仁

并行線程

人們常常把 “異步” 和 “并行” 這兩個術(shù)語混淆,但它們其實(shí)完全不同虽另。記住暂刘,異步是關(guān)于 “現(xiàn)在” 和 “將來” 之間的間隙,而并行說的是能夠同時發(fā)生的事情洲赵。

進(jìn)程和線程是并行計算最常用的工具鸳惯。進(jìn)程和線程獨(dú)立運(yùn)行,也可能同時運(yùn)行:在多個獨(dú)立的處理器上叠萍,或者多個獨(dú)立的計算機(jī)上芝发,而多線程可以在同一個進(jìn)程上共享內(nèi)存。

相比之下苛谷,事件循環(huán)將工作分成多個任務(wù)辅鲸,并串行執(zhí)行它們,不允許對共享內(nèi)存做并行訪問和改變腹殿。通過獨(dú)立線程中的相互協(xié)作的事件循環(huán)独悴,并行化和 “串行化” 可以共存。

并行線程的交錯執(zhí)行和異步事件的交錯執(zhí)行锣尉,其顆粒度是完全不同的刻炒。

比如:

function later() {
    answer = answer * 2;
    console.log( "Meaning of life:", answer );
}

雖然 later() 的全部內(nèi)容會被作為一個事件循環(huán)隊列的入口,然而考慮這段代碼運(yùn)行在一個線程上自沧,實(shí)際上可能有許多不同的底層操作坟奥。比如,answer = answer * 2 要先加載 answer 的當(dāng)前值拇厢,接著把 2 放在某處爱谁,然后做乘法計算,然后取出結(jié)果孝偎,并把結(jié)果存回 answer 中访敌。

在單線程環(huán)境下,線程隊列中的這些項目是底層操作其實(shí)是無所謂的衣盾,因?yàn)?br> 線程不能被中斷寺旺。但如果你有一個并行系統(tǒng),其中兩個不同的線程在同一個程序中運(yùn)行势决,你很可能得到意想不到的結(jié)果迅涮。

考慮:

var a = 20;

function foo() {
    a = a + 1;
}

function bar() {
    a = a * 2;
}

// ajax(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );

鑒于 JavaScript 的單線程特性,如果 foo()bar() 之前運(yùn)行徽龟, a 計算的結(jié)果為 42叮姑,但如果 bar()foo()之前運(yùn)行,a 的結(jié)果將是 41据悔。

如果共享相同數(shù)據(jù)的 JS 事件并行執(zhí)行传透,那么問題就更加微妙了〖牵考慮下面兩列偽碼朱盐, foo()bar() 代碼運(yùn)行時所在的線程執(zhí)行的是下面兩列偽碼,思考他們正好同一時刻運(yùn)行會發(fā)生什么菠隆。

線程 1 (XY 是臨時內(nèi)存地址)

foo():
  a. load value of `a` in `X`
  b. store `1` in `Y`
  c. add `X` and `Y`, store result in `X`
  d. store value of `X` in `a`

線程 2 (XY 是臨時內(nèi)存地址)

bar():
  a. load value of `a` in `X`
  b. store `2` in `Y`
  c. multiply `X` and `Y`, store result in `X`
  d. store value of `X` in `a`

現(xiàn)在兵琳,讓我們假設(shè)這兩個線程真的在并行運(yùn)行狂秘。你可能會發(fā)現(xiàn)問題所在,對嗎躯肌?它們在臨時步驟中使用了共享的內(nèi)存地址 XY 者春。

如果上述步驟是這樣的, a 的最終結(jié)果又是什么呢清女?

1a  (load value of `a` in `X`   ==> `20`)
2a  (load value of `a` in `X`   ==> `20`)
1b  (store `1` in `Y`   ==> `1`)
2b  (store `2` in `Y`   ==> `2`)
1c  (add `X` and `Y`, store result in `X`   ==> `22`)
1d  (store value of `X` in `a`   ==> `22`)
2c  (multiply `X` and `Y`, store result in `X`   ==> `44`)
2d  (store value of `X` in `a`   ==> `44`)

a 的結(jié)果將是 44钱烟。那這樣排列呢?

1a  (load value of `a` in `X`   ==> `20`)
2a  (load value of `a` in `X`   ==> `20`)
2b  (store `2` in `Y`   ==> `2`)
1b  (store `1` in `Y`   ==> `1`)
2c  (multiply `X` and `Y`, store result in `X`   ==> `20`)
1c  (add `X` and `Y`, store result in `X`   ==> `21`)
1d  (store value of `X` in `a`   ==> `21`)
2d  (store value of `X` in `a`   ==> `21`)

a 的結(jié)果將是 21嫡丙。

所以拴袭,多線程編程是相當(dāng)復(fù)雜的,因?yàn)槿绻悴徊扇√厥獠襟E去防止此類沖突和交錯的發(fā)生的話曙博,你會的到非常意外的不確定的結(jié)果拥刻,這常常讓人頭痛不已。

JavaScript 從不跨線程共享數(shù)據(jù)父泳,這意味著我們不需要擔(dān)心上述的不確定性泰佳。但也不意味著 JS 總是確定的域仇。還記得前面提到的嗎压储?foo()bar() 的相對排列順序?qū)е铝瞬煌慕Y(jié)果(41 或 42)。

注意: 可能至今還不明顯窿凤,但不是所有的不確定性都是有害的睬捶。它有時無關(guān)緊要黔宛,有時是我們刻意而為的。通過本章節(jié)的接下來的一些章節(jié)擒贸,我們將看到更多的這方面的例子臀晃。

完整運(yùn)行

由于 JavaScript 的單線程特性,foo()bar() 內(nèi)部的代碼是原子的介劫,這意味著一旦 foo() 開始運(yùn)行徽惋,必須要等到這段代碼全部執(zhí)行完畢, bar() 中的代碼才能開始運(yùn)行座韵,反之亦然险绘。這被稱為 “完整運(yùn)行” 行為。

事實(shí)上誉碴,如果 foo()bar() 包含更多的代碼宦棺,完整運(yùn)行的語義會更清晰,比如:

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(..) is some arbitrary Ajax function given by a library
ajax( "http://some.url.1", foo );
ajax( "http://some.url.2", bar );

因?yàn)?foo() 不能被 bar() 中斷黔帕,bar() 也不能被 foo() 中斷代咸,所以這個程序只有兩種可能的輸出,這取決于哪個先運(yùn)行——如果存在多線程成黄,而且 foo()bar() 中的語句可以交替運(yùn)行的話呐芥,可能輸出結(jié)果的數(shù)量將大大增加逻杖!

塊 1 是同步的(當(dāng)前執(zhí)行),但 塊 2 和 3 是異步的(將來執(zhí)行)思瘟,這意味著他們的執(zhí)行在時間上是分割的荸百。

塊 1:

var a = 1;
var b = 2;

塊 2(foo()):

a++;
b = b * a;
a = b + 3;

塊 3(bar()):

b--;
a = 8 + b;
b = a * 2;

塊 2 和 塊 3 的執(zhí)行順序不確定,所以這段程序有兩個可能的輸出潮太,如下所示:

輸出 1:

var a = 1;
var b = 2;

// foo()
a++;
b = b * a;
a = b + 3;

// bar()
b--;
a = 8 + b;
b = a * 2;

a; // 11
b; // 22

輸出 2:

var a = 1;
var b = 2;

// bar()
b--;
a = 8 + b;
b = a * 2;

// foo()
a++;
b = b * a;
a = b + 3;

a; // 183
b; // 180

同一段代碼有兩種輸出管搪,這意味著仍然存在著不確定性虾攻!但這種不確定性是函數(shù)(事件)順序級別的铡买,而不是多線程下的語句順序級別(或者說,其實(shí)是表達(dá)式執(zhí)行順序級別)霎箍。換句話說奇钞,這比多線程還是要確定的多。

在 JavaScript 的特性中漂坏,這種函數(shù)順序不確定性就是通常所說的 “競態(tài)條件”景埃,因?yàn)?foo() 和 bar() 在相互競爭,來看看誰先運(yùn)行顶别。具體來說谷徙,正因?yàn)闊o法可靠預(yù)測 a 和 b 的計算結(jié)果,所以它才是 “競態(tài)條件”驯绎。

注意:如果 JS 中有個函數(shù)完慧,它無論如何也不具備完全執(zhí)行的特性,我們會有更多種可能的輸出剩失,對吧屈尼?ES6 就引入了這樣一個東西(見第四章 “Generators”),但現(xiàn)在別擔(dān)心拴孤,我們以后會介紹這一部分的脾歧!


本系列下一部分將介紹 “并發(fā)”

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市演熟,隨后出現(xiàn)的幾起案子鞭执,更是在濱河造成了極大的恐慌,老刑警劉巖芒粹,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚕冬,死亡現(xiàn)場離奇詭異,居然都是意外死亡是辕,警方通過查閱死者的電腦和手機(jī)囤热,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來获三,“玉大人旁蔼,你說我怎么就攤上這事锨苏。” “怎么了棺聊?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵伞租,是天一觀的道長。 經(jīng)常有香客問我限佩,道長葵诈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任祟同,我火速辦了婚禮作喘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晕城。我一直安慰自己泞坦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布砖顷。 她就那樣靜靜地躺著贰锁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滤蝠。 梳的紋絲不亂的頭發(fā)上豌熄,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機(jī)與錄音物咳,去河邊找鬼锣险。 笑死,一個胖子當(dāng)著我的面吹牛所森,可吹牛的內(nèi)容都是我干的囱持。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焕济,長吁一口氣:“原來是場噩夢啊……” “哼纷妆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晴弃,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤掩幢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后上鞠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體际邻,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年芍阎,在試婚紗的時候發(fā)現(xiàn)自己被綠了世曾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡谴咸,死狀恐怖轮听,靈堂內(nèi)的尸體忽然破棺而出骗露,到底是詐尸還是另有隱情,我是刑警寧澤血巍,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布萧锉,位于F島的核電站,受9級特大地震影響述寡,放射性物質(zhì)發(fā)生泄漏柿隙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一鲫凶、第九天 我趴在偏房一處隱蔽的房頂上張望禀崖。 院中可真熱鬧,春花似錦掀序、人聲如沸帆焕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至财饥,卻和暖如春换吧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钥星。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工沾瓦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谦炒。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓贯莺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宁改。 傳聞我的和親對象是個殘疾皇子缕探,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容