Help us understand the problem. What is going on with this article?

[Node.js] イベントループの仕組みを探る 〜 JSおくのほそ道 #002

More than 5 years have passed since last update.

こんにちはほそ道です。
今回はイベントもといイベントループを取り上げてみます。

目次はこちら

イベント駆動処理の作成

とりあえず作ってみよう

設計と実装

ほそ道は毎週ボウリングに行くのですが、
自宅やオフィスにボウリングレーンがあったら夢のようだ。。
という夢をかなえる為に ボウリングレーンを実装 してみました。
ストライクやガターの発生をイベントとして検知したら
ディスプレイになんやかんや表示するという感じのシロモノです。
雰囲気を出す為にconsole.log関数をレーンのディスプレイに見立ててみました。
ちなみに見やすさ重視のためメモリ効率等はいっさい考慮してません。

javascript
var EventEmitter = require('events').EventEmitter;

// define Lane
function Lane01() {
  this.display = console.log;
  this.detect = function(n) {
    this.emit(n.toString() + 'pins');
  };

  // events
  this.on('10pins', function() {
    this.display("Strike! Congratulations!");
  });
  this.on('0pins', function() {
    this.display("Gutter... Don't mind...");
  });
}
Lane01.prototype = new EventEmitter();

// Let's play!
var lane01 = new Lane01();
lane01.detect(10);
lane01.detect(0);
console.log("Thanks to Play!");

解説

this.display = console.log;で関数の参照を渡してますが
出力をレーンのディスプレイに見立てるというモデリング的なものなので軽く流してください。。
EventEmitterというイベントを実装する為の機能を取り込みます。
これをボウリングレーンのprototypeに突っ込んでレーンをイベント駆動オブジェクトとします。
インスタンス.on('イベント名', コールバック)としてやればイベントが実装されます。
インスタンス.emit('イベント名')でイベントが発生します。
さて、出力結果を見てみましょう。

出力結果
Strike! Congratulations!
Gutter... Don't mind...
Thanks to Play!

うん、いいね!

。。。おや。
そりゃそうだよな、と思いつつちょっとだけシュン。。
イベント実装しただけだと非同期処理になるわけじゃないんだね。。
最終行に書いた出力「Thanks to Play!」はふっつーに最後に出てますね。。
前回も取り上げましたがI/Oは非同期+ワーカスレッド実行にした方がグッドパフォーマンスな訳で。
これだとたくさんの人がプレーするとディスプレイもレーンの検知も遅延しちゃうかもしれませんね。。

イベント駆動非同期処理の作成

というわけで作ってみよう

改良型レーンの実装+出力結果

実装してみました。出力結果まで合わせて見てみましょう。

javascript
var EventEmitter = require('events').EventEmitter;

// define Lane
function Lane02() {
  this.display = console.log;
  this.detect = function(n) {
    this.emit(n.toString() + 'pins');
  };

  // events
  this.on('10pins', function() {
    process.nextTick(this.display.bind(this, "Strike! Congratulations!"));
  });
  this.on('0pins', function() {
    process.nextTick(this.display.bind(this, "Gutter... Don't mind..."));
  });
}
Lane02.prototype = new EventEmitter();

// Let's play!
var lane02 = new Lane02();
lane02.detect(10);
lane02.detect(0);
console.log("Thanks to Play!");
出力結果
Thanks to Play!
Strike! Congratulations!
Gutter... Don't mind...

「Thanks to Play!」が最初に出ている!
というわけで 非同期(ノンブロッキング)イベント型レーン に改良されました!
最初にサンクスメッセージが出るので
なんとなく「帰れ!」と言われてるみたいな感じがするツンデレレーンですが。
たくさんの人が投げても遅延しにくくなっているはず!(少なくとも前よりは)

コード解説

ほとんど1番レーンと変化はありません。
イベントのコールバックの処理を
process.nextTick(this.display.bind(this, "Strike! Congratulations!"));
に変えただけです。
displayの引数をbindで渡しているのは単なる調整です。
大事なのは、そう、process.nextTickなんです!

イベントループ!

Node.jsのイベントループ

「大体こんな感じじゃね?」みたいな部分も結構ありますが力強く説明してみます。

Node.jsのメインスレッドは起動すると、記述コードの実行よりも先に
イベント検出/実行をコントロールする「イベントループ」を起動します。

そして「イベントキュー」というヤツがいて、こいつにイベントが投入されます。
イベントキューにコールバック関数を突っ込むのが先ほど登場した`process.nextTick`!

イベントは「イベントキュー」内のキューとして取り出されます。

イベントループは超短い周期でぐるぐる回り、
イベントが検出でき、メインスレッドのタスクがなくなったら
FIFOでキュー(コールバック)を取り出してコールバックを実行です!

すなわち
こんな感じになっとるはずです。(余計わかりにくいかもしれない)
eventloop.png

ブラウザもイベントループしてるよね

ちなみにブラウザジャバスクリプタの皆様もイベントループといったら
FireFoxにはタイマースレッドってヤツがいて、みたいな話や
addEventListenersetTimeoutなんかでおなじみだと思います。
ほそ道は基本的にはあの感じをイメージしてます。
じつはNode.jsでもsetTimeout(callback, 0)みたいな事をすれば
process.nextTickと同じ動きになります。
しかし、ちゃんとイベントループ設計に乗っかって実装されているのはprocess.nextTickの方で
パフォーマンスは雲泥の差らしいです。

はい。
今回は以上でございます!
御付き合い頂き有り難う御座いました!

非同期/ノンブロッキングなトピックのまとめ

今回、JS野郎のたしなみとしてNodeのベースとなるアーキテクチャに触れてみました。
ハイパフォーマンス実現に向けた設計思想がとんがりつつ明確で、
本格的にモノ造りするときはブロックが発生してないかや、どこをコールバックとすべきかなど、
思想に沿っているかを意識しつつモデリングしていく事が重要なポイントになりそうですね。
ブラウザJSにズブズブだったほそ道にとっては共通点・違い含めて非常に新鮮です。

次回は無視し続けてきた予約語require。モジュールのお話をします!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away