28
そそそそそそそ そそそそそ process.nextTick() Node.js そそそそそそそそそそそそそ IIJ そそ そそ (@jovi0608) 2012 そ 6 そ 28 そ そそ Node そそ 6 そそそ

そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

Embed Size (px)

Citation preview

Page 1: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

そうだったのか! よくわかる process.nextTick()

Node.js のイベントループを理解する

IIJ 大津 繁樹 (@jovi0608)2012 年 6 月 28 日

東京 Node 学園 6 時限目

Page 2: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する
Page 3: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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 の歩み ( 参考)

Page 4: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

今日の話• Node のイベントループとは• process.nextTick とは• node-dev での大論争• 今後どうなる?• process.nextTick の正しい使い

おそらく世界初?のNode-v0.8 ベースでイベントループを解説(libuv の大幅な変更に追随 )( 注: 説明は Linux が対象です。 )

Page 5: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

Node のイベントループとは、

• Node の心臓

イベントループが終了したら Node は死にます。

Page 6: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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)

Page 7: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

イベントループが回り続けるには

アクティブな handle/req がなければ

イベントループが終了

https://github.com/joyent/node/blob/v0.8.0-release/deps/uv/src/unix/core.c#L252-261

Page 8: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

handle と req の違い• handle– I/O が発生してない時でもイベントループを

維持– (例 ) server.listen()

• req– I/O が発生している時だけイベントループを

維持– ( 例 ) http.get()

Page 9: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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 アドレス情報取得

後で見ておいて下

さい。

Page 10: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

実際のコードでは、(その1)var http = require('http');var server = http.createServer();

アクティブハンドルが無いから Node 終了

アクティブハンドル

0

Page 11: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

実際のコードでは、(その2)var http = require('http');var server = http.createServer();server.listen(1234);

アクティブハンドルが作成され Nodeは終了しない。実際は epoll wait (Linux) してます。

アクティブハンドル追加

(+1)

Page 12: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

実際のコードでは、(その3)var http = require('http');var server = http.createServer();server.listen(1234, function() { server.close();});

アクティブハンドルがすぐ無効化されるので Node 終了

アクティブハンドル削除

(+1-1=0)

Page 13: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

イベントループの中身

1. 時刻更新2. タイマー実行3. アイドル実行4. Prepare 実行5. I/O イベント実行

(libev)6. Check 実行7. ハンドル終了

7つのステップ

Page 14: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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 から始まる。)

ユーザプログラム

Page 15: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

イベントループを止めてはいけない!

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)

ずっとここで止まる!

Page 16: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

なぜ 3 カ所も nextTick() があるの?

Page 17: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

理由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() が先に呼ばれる( 注: 将来仕様が変わる可能性があります。)

Page 18: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

理由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 から始まる。)

Page 19: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

理由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() が先に呼ばれる

( 注: 将来仕様が変わる可能性があります。)

Page 20: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

理由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 から始まる。)

Page 21: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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倍の差

おそらくリンクリストの生成と時刻取得のオーバヘッドによるものだろう(未確認)

Page 22: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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);});

Page 23: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

node-dev での大論争推進派

• 今までの動作がそもそもおかしかった。正しい動作に変えるだけ

• CPU 処理の分散のために再帰を使うのは悪いこと、 child process を使え

• idle 用リスナの用途に再帰を使うのはわからんでもないが、 setTimeout を使え

• API 名を変えるのはもう遅い• 実際に I/O の取りこぼしでバグ

が出ている。この変更でそれを直すのが優先する

擁護派• 別の API にすればいいじゃな

いか• 実際にコード変更するのがど

んなに大変か• どうせ今さら何言っても聞き

入れてくれないだろう

Page 24: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

今後どうなるのか(想像 )1: 時刻更

新7: ハンドル終了

5:poll

始まり終わり

4:run_prepare

setTimeout()

コールバック

3:run_idle

イベントループ一周 (Tick)

libev+kernelepoll: Linuxkqueue: BSDevent port: Solaris

2:run_timers6:run_checknextTick()全展開

nextTick()全展開

再帰は一定回数繰り返したら遅延させるかも

Page 25: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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');});

非同期イベントの

生成

Page 26: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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);});

非同期コールバックの呼び出

Page 27: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

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消費処理は子プロセス

Page 28: そうだったのか! よくわかる process.nextTick() node.jsのイベントループを理解する

まとめ• Node のイベントループの仕組みを良く理解

した上でイベントループを止めないことを意識してコードを書きましょう。

• process.nextTick() は、– 非同期イベントの発生– 非同期コールバックの実行の用途で使いましょう。

• CPU を消費する処理には、 child process を利用しましょう。

• node-v0.9 では process.nextTick() の動作仕様が変わる予定です。