そうだったのか! よくわかる process.nexttick()...
Post on 20-Aug-2015
27.930 Views
Preview:
TRANSCRIPT
そうだったのか! よくわかる process.nextTick()
Node.js のイベントループを理解する
IIJ 大津 繁樹 (@jovi0608)2012 年 6 月 28 日
東京 Node 学園 6 時限目
2007/10 libev 公開2008/05 libeio 公開2009/09 Google V8 公開2009/02 ry Node 開発開始2009/05 node-v0.0.1 リリース2009/06 nodejs ML 開始2009/10 npm 公開2009/11 JSConf EU ry 発表2010/04 Heroku サポート2010/08 node-v0.2.0 リリース
2010/08 nodejs_jp 開始2010/09 no.de 開始2010/11 Joyent 管轄へ2011/02 node-v0.4.0 リリース2011/03 東京 Node 学園 #12011/10 東京 Node 学園祭2011/11 node-v0.6.0 リリース2011/12 Azure サポート2012/01 isaacs 管理へ2012/06 node-v0.8.0 リリース
Node の歩み ( 参考)
今日の話• Node のイベントループとは• process.nextTick とは• node-dev での大論争• 今後どうなる?• process.nextTick の正しい使い
方
おそらく世界初?のNode-v0.8 ベースでイベントループを解説(libuv の大幅な変更に追随 )( 注: 説明は Linux が対象です。 )
Node のイベントループとは、
• Node の心臓
イベントループが終了したら Node は死にます。
Node のイベントループの正体
https://github.com/joyent/node/blob/v0.8.0-release/deps/uv/src/unix/core.c#L265
Node が起動する時に uv_run() が呼ばれます。 (src/node.cc:2910)
イベントループが回り続けるには
アクティブな handle/req がなければ
イベントループが終了
https://github.com/joyent/node/blob/v0.8.0-release/deps/uv/src/unix/core.c#L252-261
handle と req の違い• handle– I/O が発生してない時でもイベントループを
維持– (例 ) server.listen()
• req– I/O が発生している時だけイベントループを
維持– ( 例 ) http.get()
handle と req の種類handle
ASYNC 非同期ジョブの操作CHECK ループの最後の操作FS_EVENT ファイルイベント操作FS_POLL stat の問い合わせ操作IDLE アイドルの時の操作NAMED_PIPE 名前付きパイプの操作POLL fd イベントの操作PREPARE ループの最初の操作PROCESS プロセスの操作TCP TCP の操作TIMER タイマー操作TTY TTYP の操作UDP UDP の操作
req
CONNECT stream 接続WRITE stream 書き込みSHUTDOWN stream 停止UDP_SEND udp 送信FS ファイル操作WORK ワーカスレッドGETADDRINFO アドレス情報取得
後で見ておいて下
さい。
実際のコードでは、(その1)var http = require('http');var server = http.createServer();
アクティブハンドルが無いから Node 終了
アクティブハンドル
0
実際のコードでは、(その2)var http = require('http');var server = http.createServer();server.listen(1234);
アクティブハンドルが作成され Nodeは終了しない。実際は epoll wait (Linux) してます。
アクティブハンドル追加
(+1)
実際のコードでは、(その3)var http = require('http');var server = http.createServer();server.listen(1234, function() { server.close();});
アクティブハンドルがすぐ無効化されるので Node 終了
アクティブハンドル削除
(+1-1=0)
イベントループの中身
1. 時刻更新2. タイマー実行3. アイドル実行4. Prepare 実行5. I/O イベント実行
(libev)6. Check 実行7. ハンドル終了
7つのステップ
Node-v0.8 イベントループ概要1: 時刻更
新7: ハンドル終了
5:poll
始まり終わり
4:run_prepare
nextTick()
setTimeout()
コールバック
3:run_idle
イベントループ一周 (Tick)
libev+kernelepoll: Linuxkqueue: BSDevent port: Solaris
2:run_timers6:run_check
nextTick()
nextTick()
(注: ユーザプログラムは 3: run_idle から始まる。)
ユーザプログラム
イベントループを止めてはいけない!
1: 時刻更新7: ハンドル終
了
5:poll
始まり終わり
4:run_prepare
setTimeout()
コールバック
3:run_idle
こんなコードはダメ!while(1) { console.log(‘hoge’);}
libev+kernelepoll: Linuxkqueue: BSDevent port: Solaris
2:run_timers6:run_check
nextTick()
nextTick()
(注: ユーザプログラムは 3: run_idle から始まる。)
while(1)
ずっとここで止まる!
なぜ 3 カ所も nextTick() があるの?
理由1:呼び出し順番setTimeout(function(){ console.log(‘3:foo’);}, 0);process.nextTick(function() { console.log(‘2:hoge’);});console.log(‘1:piyo’);
$ node tick-order.js1:piyo2:hoge3:foo
setTimeout() より process.nextTick() が先に呼ばれる( 注: 将来仕様が変わる可能性があります。)
理由1:呼び出し順番1: 時刻更
新7: ハンドル終了
5:poll
始まり終わり
4:run_prepare
nextTick()
setTimeout()
コールバック
3:run_idle
イベントループ一周 (Tick)
2:run_timers6:run_check
nextTick()
nextTick()
console.log(‘1:piyo’)
console.log(‘2:hoge’)
console.log(‘3:foo’)
(注: ユーザプログラムは 3: run_idle から始まる。)
理由2:入れ子の呼び出し順番process.nextTick(function() { setTimeout(function(){ console.log(‘4:foo'); }, 0); process.nextTick(function() { console.log(‘3:hoge'); }); console.log(‘2:bar');});console.log(‘1:piyo’);
$ node tick-order2.js1:piyo2:bar3:hoge4:foo
process.nextTick() のスコープ内でも setTimeout() より process.nextTick() が先に呼ばれる
( 注: 将来仕様が変わる可能性があります。)
理由2:入れ子の呼び出し順番
1: 時刻更新7: ハンドル終
了
5:poll
始まり終わり
4:run_prepare
nextTick()
setTimeout()
コールバック
3:run_idle
イベントループ一周 (Tick)
2:run_timers6:run_check
nextTick()
nextTick()
console.log(‘2:bar’)
console.log(‘4:foo’)console.log(‘3:hoge’)
console.log(‘1:piyo’)
(注: ユーザプログラムは3: run_idle から始まる。)
process.nextTick() の説明(マニュアルより)
イベントループの次以降のループでコールバックを呼び出します。 これは setTimeout(fn, 0) の単純なエイリアスではなく、 はるかに効率的です。
for (var i = 0; i < 1024*1024; i++) { process.nextTick(function (){ Math.sqrt(i); } );}
for (var i = 0; i < 1024 * 1024; i++) { setTimeout(function () { Math.sqrt(i) }, 0);}
0.360u 0.072s 0:00.44 97.7%
1.700u 0.800s 0:02.51 99.6%
処理時間
約5倍の差
おそらくリンクリストの生成と時刻取得のオーバヘッドによるものだろう(未確認)
node-v0.9 に向けて isaacs からの提案• process.nextTick() でイベントハンドラを追加するのはよくやること
だけど次のイベントループでハンドラが登録されるまでの間にイベントが発生したりすると I/O の取りこぼしが起きてしまう。
• 次のイベントが発生する前に確実にハンドラを登録をするために、V8 で JS を実行した直後に process.nextTick() に登録された関数を全部実行するようにしたい。
• 再帰処理とかの展開もそこで行うので次のようなコードでは setTimeout() は起動しなくなるよ。
setTimeout(function() { console.log('timeout');}, 1000);process.nextTick(function f() { process.nextTick(f);});
node-dev での大論争推進派
• 今までの動作がそもそもおかしかった。正しい動作に変えるだけ
• CPU 処理の分散のために再帰を使うのは悪いこと、 child process を使え
• idle 用リスナの用途に再帰を使うのはわからんでもないが、 setTimeout を使え
• API 名を変えるのはもう遅い• 実際に I/O の取りこぼしでバグ
が出ている。この変更でそれを直すのが優先する
擁護派• 別の API にすればいいじゃな
いか• 実際にコード変更するのがど
んなに大変か• どうせ今さら何言っても聞き
入れてくれないだろう
今後どうなるのか(想像 )1: 時刻更
新7: ハンドル終了
5:poll
始まり終わり
4:run_prepare
setTimeout()
コールバック
3:run_idle
イベントループ一周 (Tick)
libev+kernelepoll: Linuxkqueue: BSDevent port: Solaris
2:run_timers6:run_checknextTick()全展開
nextTick()全展開
再帰は一定回数繰り返したら遅延させるかも
process.nextTick の正しい使い方var events = require('events');var util = require('util');function Hoge() { var self = this; process.nextTick(function() { self.emit('foo'); });}util.inherits(Hoge, events.EventEmitter);var hoge = new Hoge();hoge.on('foo', function() { console.log('foo event emitted');});
非同期イベントの
生成
process.nextTick の正しい使い方var events = require('events');var util = require('util');function Hoge(cb) { if(cb) { process.nextTick(function() { cb(); }); }}util.inherits(Hoge, events.EventEmitter);Hoge.prototype.setfoo = function(arg) { this.foo = arg;};var hoge = new Hoge(function() { hoge.setfoo('bar'); console.log(hoge.foo);});
非同期コールバックの呼び出
し
process.nextTick の再帰を避けるvar cluster = require('cluster');if (cluster.isMaster) { var worker = cluster.fork(); worker.on('message', function(msg) { console.log(msg); });} else { // 子プロセス while(1) { process.send(‘hoge’); }}
CPU消費処理は子プロセス
で
まとめ• Node のイベントループの仕組みを良く理解
した上でイベントループを止めないことを意識してコードを書きましょう。
• process.nextTick() は、– 非同期イベントの発生– 非同期コールバックの実行の用途で使いましょう。
• CPU を消費する処理には、 child process を利用しましょう。
• node-v0.9 では process.nextTick() の動作仕様が変わる予定です。
top related