事件/異步
Promise
-
-
Promise對象的三個狀態(tài)
has-resolution
,has-rejection
,unresolved
-
.then()
方法是異步調(diào)用的。
var promise = new Promise(function (resolve){ console.log("inner promise"); // 1 resolve(42); }); promise.then(function(value){ console.log(value); // 3 }); console.log("outer promise"); // 2 // 輸出 inner promise // 1 outer promise // 2 42 // 3
-
Promise對象的三個狀態(tài)
-
其實在我的理解當(dāng)中丝格,同步是相對的撑瞧。對一串連續(xù)的事件使用promise封裝,他們之間是同步實行的显蝌,并不是對于外部预伺。我們再來解釋下這個例子:
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5); // 輸出 2 3 5 4 1
這里會用到
Event Loop
的知識,后面有詳細說明曼尊。首先代碼順序執(zhí)行酬诀,注冊了一個Timer事件,將Timer事件放入Timer隊列中骆撇。然后有一個promise被放入poll
隊列中執(zhí)行瞒御。先是有一個輸出2的同步事件,被放入poll
隊列執(zhí)行神郊,再就出現(xiàn)了resolve()
肴裙,一個異步事件先放入i/o callback隊列中趾唱,再有一個輸出3的同步事件,被放入poll
隊列執(zhí)行蜻懦。再是將輸出5的同步事件放入poll
執(zhí)行鲸匿,poll
空了以后,將i/o callback中的resolve()
放入poll
執(zhí)行阻肩,最后將Timer中的事件放入poll
中執(zhí)行。
Events
Events中的emit是同步的运授,會按照注冊順序來觸發(fā)監(jiān)聽器烤惊。
一個事件監(jiān)聽器中監(jiān)聽同一個事件,會導(dǎo)致死循環(huán)吁朦?
const EventEmitter = require('events');
let emitter = new EventEmitter();
emitter.on('myEvent', () => {
console.log('hi');
emitter.emit('myEvent');
});
emitter.emit('myEvent');
- 這種情況是會死循環(huán)的柒室,其實就是無限的遞歸調(diào)用,運行一下果然崩棧了逗宜。
Maximum call stack size exceeded
const EventEmitter = require('events');
let emitter = new EventEmitter();
emitter.on('myEvent', function sth () {
emitter.on('myEvent', sth);
console.log('hi');
});
emitter.emit('myEvent');
-
通過源碼解析 Node.js 中 events 模塊里的優(yōu)化小細節(jié)
- 這種情況不會出現(xiàn)死循環(huán)雄右,因為在執(zhí)行的過程當(dāng)中是把原監(jiān)聽數(shù)組拷貝一份出來執(zhí)行監(jiān)聽。當(dāng)在監(jiān)聽器中監(jiān)聽同一個事件的時候纺讲,只會在原監(jiān)聽數(shù)組當(dāng)中添加監(jiān)聽擂仍,而不會在這個拷貝后的數(shù)組添加。
Event Loop
-
JavaScript 運行機制詳解:再談Event Loop
-
任務(wù)隊列
- 所有同步任務(wù)都在主線程上執(zhí)行熬甚,形成一個執(zhí)行棧(execution context stack)逢渔。
- 主線程之外,還存在一個"任務(wù)隊列"(task queue)乡括。只要異步任務(wù)有了運行結(jié)果肃廓,就在"任務(wù)隊列"之中放置一個事件。
- 一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢诲泌,系統(tǒng)就會讀取"任務(wù)隊列"盲赊,看看里面有哪些事件。那些對應(yīng)的異步任務(wù)敷扫,于是結(jié)束等待狀態(tài)哀蘑,進入執(zhí)行棧,開始執(zhí)行葵第。
- 主線程不斷重復(fù)上面的第三步递礼。
- 所以異步任務(wù)永遠是在同步任務(wù)之后開始執(zhí)行的,不管他的代碼位置如何羹幸。
- 排在任務(wù)隊列前面的事件優(yōu)先進入執(zhí)行棧脊髓,但是有些事件會被設(shè)置定時器,如果現(xiàn)在的時間小于了定時器的時間栅受,那么會一直等到到達定時器的時間才會將事件加入執(zhí)行棧将硝。如果現(xiàn)在的時間大于了定時器的時間恭朗,也不會立即將它加入執(zhí)行棧,會等到同步任務(wù)與它前面的異步任務(wù)執(zhí)行完成以后再將它加入執(zhí)行棧依疼。
-
process.nextTick
- 在當(dāng)前"執(zhí)行棧"的尾部痰腮,下一次Event Loop(主線程讀取"任務(wù)隊列")之前,觸發(fā)回調(diào)函數(shù)律罢。也就是說膀值,它指定的任務(wù)總是發(fā)生在所有異步任務(wù)之前。
- 如果有多個
process.nextTick
語句(不管它們是否嵌套)误辑,將全部在當(dāng)前"執(zhí)行棧"執(zhí)行沧踏。
-
setImmediate
- 在當(dāng)前"任務(wù)隊列"的尾部添加事件,也就是說巾钉,它指定的任務(wù)總是在下一次Event Loop時執(zhí)行翘狱,這與
setTimeout(fn, 0)
很像。 -
process.nextTick
和setImmediate
的一個重要區(qū)別:多個process.nextTick
語句總是在當(dāng)前"執(zhí)行棧"一次執(zhí)行完砰苍,多個setImmediate可能則需要多次loop才能執(zhí)行完潦匈。事實上,這正是Node.js 10.0版添加setImmediate
方法的原因赚导,否則像下面這樣的遞歸調(diào)用process.nextTick
茬缩,將會沒完沒了,主線程根本不會去讀取"事件隊列"吼旧!
- 在當(dāng)前"任務(wù)隊列"的尾部添加事件,也就是說巾钉,它指定的任務(wù)總是在下一次Event Loop時執(zhí)行翘狱,這與
-
-
Node.js Event Loop 的理解 Timers寒屯,process.nextTick()
-
這一篇對于
Event Loop
的分析就更加進階了,我們首先看Event Loop
的各個階段黍少。┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
-
timers: 執(zhí)行各種定時器預(yù)約的操作寡夹,
setTimeout(callback)
和setInterval(callback)
-
I/O callbacks: 執(zhí)行除了
close
事件的callback和屬于timers
的callback以外的callback事件。 - idle, prepare: 僅node內(nèi)部使用厂置。
-
poll: 獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里;(在我的理解中菩掏,這個階段才是一個
Event Loop
的開始) -
check: 執(zhí)行
setImmediate()
設(shè)定的callbacks; -
close callbacks:
close
事件的回調(diào)會在該階段執(zhí)行。 - 而還有一個獨立于
Event Loop
外的過程就是process.nextTick()
它是在各個階段的切換階段進行調(diào)用昵济。
-
timers: 執(zhí)行各種定時器預(yù)約的操作寡夹,
-
Poll階段
- 代碼中沒有設(shè)置
timer
:- poll quenue 不為空時智绸,同步執(zhí)行隊列中的所有事件。直到隊列為空访忿,或執(zhí)行的callback達到系統(tǒng)的上限瞧栗。
-
poll quenue 為空時,如果設(shè)定了
setImmediate()
則進入 check階段海铆,沒有設(shè)定的話迹恐,就阻塞等待callback進入隊列。
- 代碼中設(shè)置
timer
:-
poll queue進入空狀態(tài)時卧斟,
event loop
將檢查timers,如果有1個或多個timers時間時間已經(jīng)到達殴边,event loop
將按循環(huán)順序進入timers階段憎茂,并執(zhí)行timer queue.
-
poll queue進入空狀態(tài)時卧斟,
- 代碼中沒有設(shè)置
-
進程
Process
-
process.cwd()
:返回運行當(dāng)前腳本的工作目錄的路徑。 -
process.chdir()
:改變工作目錄锤岸。
process.nextTick
這個在上面已經(jīng)分析過了竖幔,不贅述。
標(biāo)準(zhǔn)流
-
console.log
的實現(xiàn)exports.log = function() { process.stdout.write(format.apply(this, arguments) + '\n'); };
通過實現(xiàn)來說是偷,我覺得
console.log
是同步的拳氢。 -
同步輸入的實現(xiàn) (nodeJS 中從命令行等待并讀入用戶輸入實現(xiàn)與用戶交互的方法):
- 主要是思路是將
fs.readSync
的輸入流從定向到process.stdin.fd
- 我覺得還有一種方法是對輸入流進行監(jiān)聽
- 主要是思路是將
-
Linux 中關(guān)于進程管理的命令
-
top :
TOP
是一個動態(tài)顯示過程,即可以通過用戶按鍵來不斷刷新當(dāng)前狀態(tài).如果在前臺執(zhí)行該命令,它將獨占前臺,直到用戶終止該程序為止.比較準(zhǔn)確的說,top
命令提供了實時的對系統(tǒng)處理器的狀態(tài)監(jiān)視.它將顯示系統(tǒng)中CPU最“敏感”的任務(wù)列表.該命令可以按CPU使用.內(nèi)存使用和執(zhí)行時間對任務(wù)進行排序;而且該命令的很多特性都可以通過交互式命令或者在個人定制文件中進行設(shè)定. -
ps:
ps
命令就是最基本同時也是非常強大的進程查看命令.使用該命令可以確定有哪些進程正在運行和運行的狀態(tài)蛋铆、進程是否結(jié)束馋评、進程有沒有僵尸、哪些進程占用了過多的資源等等戒职。總之大部分信息都是可以通過執(zhí)行該命令得到的透乾。 - pstree: Linux pstree命令將所有行程以樹狀圖顯示洪燥,樹狀圖將會以 pid (如果有指定) 或是以 init 這個基本行程為根 (root),如果有指定使用者 id乳乌,則樹狀圖會只顯示該使用者所擁有的行程捧韵。
-
top :
Child Process
child_process.fork
與POSIX的 fork
有什么區(qū)別?
-
POSIX的
fork
:每天進步一點點——論fork()函數(shù)與Linux中的多線程編程- 當(dāng)程序調(diào)用
fork()
函數(shù)并返回成功之后,程序就將變成兩個進程汉操,調(diào)用fork()
者為父進程再来,后來生成者為子進程。這兩個進程將執(zhí)行相同的程序文本磷瘤,但卻各自擁有不同的棧段芒篷、數(shù)據(jù)段以及堆棧拷貝采缚。子進程的棧针炉、數(shù)據(jù)以及棧段開始時是父進程內(nèi)存相應(yīng)各部分的完全拷貝,因此它們互不影響扳抽。如果fork成功篡帕,子進程中fork的返回值是0,父進程中fork的返回值是子進程的進程號贸呢,如果fork不成功镰烧,父進程會返回錯誤。
- 當(dāng)程序調(diào)用
- 但是現(xiàn)在沒有找到
child_process.fork
如果不是對當(dāng)前父進程的拷貝楞陷,那他的具體實現(xiàn)原理是什么怔鳖。
child.kill
與 child.send
的區(qū)別
- 在談這兩者的區(qū)別的時候是需要先談一下
fork
與spawn
的區(qū)別:NODEJS硬實戰(zhàn)筆記(多進程)- 實例化一個
spawn
后,會返回一個ChildProcess
對象固蛾,該對象中包含了stdin
败砂、stdout
和stderr
流對象赌渣。而實例化一個fork
,默認(rèn)是會繼承父進程的stdin
昌犹、stdout
和stderr
流(當(dāng)然也可以打開)坚芜,并且會單獨建立一個IPC通道,使得父子進程之間可以通過監(jiān)聽message
事件來進行通信斜姥,所以fork
實際上是spawn
的一個特例鸿竖。- 而
send
是需要依賴 IPC 通道進行通信的,所以只有通過fork
的子進程才能與父進程之間使用send
通信的铸敏。kill
是通過信號系統(tǒng)來進行通信缚忧,只要操作系統(tǒng)支持信號系統(tǒng)就行。
- 而
- 實例化一個
孤兒進程與僵尸進程
子進程的結(jié)束和父進程的運行是一個異步過程,即父進程永遠無法預(yù)測子進程到底什么時候結(jié)束杈笔。
-
孤兒進程:一個父進程退出闪水,而它的一個或多個子進程還在運行,那么那些子進程將成為孤兒進程蒙具。孤兒進程將被init進程(進程號為1)所收養(yǎng)球榆,并由init進程對它們完成狀態(tài)收集工作。 在
spawn
中可以通過options.detached
指定父進程死亡后是否允許子進程存活禁筏。
+ 孤兒進程在被init進程接手以后持钉,init進程會循環(huán)地wait()
它已經(jīng)退出的子進程,來進行善后篱昔,所以孤兒進程不會有什么危害每强。 -
僵尸進程:一個進程使用
fork
創(chuàng)建子進程,如果子進程退出州刽,而父進程并沒有調(diào)用wait
或waitpid
獲取子進程的狀態(tài)信息空执,那么子進程的進程描述符仍然保存在系統(tǒng)中。這種進程稱之為僵尸進程穗椅。
+ 僵尸進程因為沒有被父進程調(diào)用wait
脆烟,進程號、退出狀態(tài)房待、運行時間等都被保存在內(nèi)存中邢羔,特別是進程號一直被占用著,而系統(tǒng)的進程號又是有限的桑孩,所以大量的僵尸進程是會帶來非常大的威脅的拜鹤。
Cluster
round-robin
其實就是找下一個空閑的 worker,但是我們可以看看 Cluster
的進化過程流椒,從最開始的自由競爭(將引起 驚群效率)敏簿,將每個連接放到 worker 競爭到由 master 獲取連接再進行分配。
進程間通信
Linux進程間通信之管道(pipe)、命名管道(FIFO)與信號(Signal)
- 從原理上惯裕,管道利用fork機制建立温数,從而讓兩個進程可以連接到同一個PIPE上。最開始的時候蜻势,讀入流和輸出流都連接在同一個進程Process 1上撑刺。當(dāng)fork復(fù)制進程的時候,會將這兩個連接也復(fù)制到新的進程(Process 2)握玛。隨后够傍,每個進程關(guān)閉自己不需要的一個連接 (一個關(guān)閉讀入流,一個關(guān)閉輸出流)挠铲,這樣冕屯,剩下的連接就構(gòu)成了PIPE。
- 由于管道只能在父子進程之間進行通信拂苹,為了解決這一問題就提供了FIFO方法連接進程安聘,FIFO (First in, First out)為一種特殊的文件類型,它在文件系統(tǒng)中有對應(yīng)的路徑瓢棒。當(dāng)一個進程以讀(r)的方式打開該文件浴韭,而另一個進程以寫(w)的方式打開該文件,那么內(nèi)核就會在這兩個進程之間建立管道音羞。FIFO的好處在于我們可以通過文件的路徑來識別管道囱桨,從而讓沒有親緣關(guān)系的進程之間建立連接。
-
信號是在軟件層次上對中斷機制的一種模擬,在原理上买决,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的碌秸。信號是異步的,一個進程不必通過任何操作來等待信號的到達憾筏,事實上,進程也不知道信號到底什么時候到達。
- 內(nèi)核給一個進程發(fā)送軟中斷信號的方法财边,是在進程所在的進程表項的信號域設(shè)置對應(yīng)于該信號的位。
- 內(nèi)核處理一個進程收到的信號的時機是在一個進程從內(nèi)核態(tài)返回用戶態(tài)時点骑。
守護進程
通過系統(tǒng)命令:Linux 守護進程的啟動方法
- 首先要明確酣难,通過使用
&
來啟動后臺任務(wù)的時候,實際上是不再繼承當(dāng)前session的stdin
黑滴,會繼續(xù)繼承stdout
和stderr
憨募。 - 然后需要明確當(dāng)用戶退出當(dāng)前session的時候,系統(tǒng)會向session發(fā)出
SIGHUP
信號袁辈,session再將該信號轉(zhuǎn)發(fā)給所有子進程菜谣,子進程收到后就會退出,所以普通的后臺進程是不能實現(xiàn)守護進程的需求的。 - 那么實現(xiàn)的三個思路是:1. 不要讓session將
SIGHUP
信號轉(zhuǎn)發(fā)給后臺任務(wù)尾膊;2.將守護任務(wù)從后臺任務(wù)列表當(dāng)中移出媳危。但是光是這樣還是不行,因為后臺任務(wù)還是在繼承該session的stdout
和stderr
冈敛,所以還需要將這兩個流重定向到外面待笑。那么第三個思路是與前兩個截然不同的,第三個是重建session莺债,將后臺任務(wù)啟動到這個新進程當(dāng)中滋觉。
通過node:Nodejs編寫守護進程
- 創(chuàng)建一個進程A。
- 在進程A中創(chuàng)建進程B齐邦,我們可以使用fork方式椎侠,或者其他方法。
- 對進程B執(zhí)行
setsid
方法措拇。- Linux進程組和會話
- 該進程變成一個新會話的會話領(lǐng)導(dǎo)我纪。
- 該進程變成一個新進程組的組長。
- 該進程沒有控制終端丐吓。
- 進程A退出浅悉,進程B由init進程接管。此時進程B為守護進程券犁。