TameJS と Fiber による非同期処理の記述 (2/2)
前回の続きで今回は Fiber の話題.ずいぶんと日が空いてしまいました.
見出し
- はじめに
- 環境
- node-fibers とは?
- Fiber の利用例
- Fiber を使った非同期処理の記述例
- 終わりに
はじめに
今回は fiber を導入し,非同期 API のコールバック周りを少し整理してみよう,ということが主題です.
node.js (というかそのエンジンである V8) は fiber をサポートしていませんが*1,node-fibers を利用することで node.js 上に fiber を導入することができます.
環境
- ubuntu 10.04 32bit (VM 上で稼働)
- node 0.6.6
- node-fibers 0.6.3
node-fibers とは?
node-fibers は node.js 上で fiber (coroutine) *2 をサポートするためのライブラリです.
[email protected] 以上の環境であれば,以下のように簡単に導入することができます ('Fiber' というリテラルが導入される).
$ npm install fibers
なお,Fiber の直接利用は "not recommended" であり,Future の利用を推奨しているようです (README.md の EXAMPLES 参照).
Future は並行処理ではおなじみの概念ですし,なかなか扱いやすそうです.ただ,generator のようなものは Fiber の直接利用が必要となります.
今回は実験と割り切って Fiber を直接利用しています.
Fiber の利用例
以下のコードはよくある generator です.サンプルとしても紹介しやすいですし,動作も理解しやすい使い方の一つです.
// sample.js require('fibers'); var inc = Fiber(function() { var i = 0; while (true) { yield(i++); // (1) } }); var i; while ((i = inc.run()) < 5) { // (2) console.log(i); }
$ node sample.js 0 1 2 3 4
(2) の run で Fiber が実行されます.Fiber 側で yield が呼び出されると,呼び出し元 (2) に制御が戻ります.このとき yield の引数を戻り値として返します.再度 run が呼び出されると,今度は (1) 以降の処理が再開されます.
このように処理を切り替えていくわけです.
node-fibers のコードを読んでみると,native でいろいろとやっていて (V8 との絡みとか,linux の場合 get/make/swapcontext とか) なかなか興味深いです.
Fiber を使った非同期処理の記述例
例として read input.txt → write output.txt のコードを取り上げます.
ナイーブに書くと以下の通りです.
var fs = require('fs'); var error_handler = function(e) { console.log(e); }; fs.readFile('input.txt', 'UTF-8', function(err, data) { if (err) { error_handler(err); return; } fs.writeFile('output.txt', data, 'UTF-8', function(err) { if (err) { error_handler(err); return; } else { console.log('成功'); // 処理が続く... } }); });
Fiber を使って少し工夫してみると,以下のように書くことができます.
// Fiber を再開するための汎用コールバック var resume_cb = function(fiber) { return function(/*...*/) { fiber.run(Array.prototype.slice.call(arguments)); }; }; var error_handler = function(e) { console.log(e); }; var main = function() { var fiber = Fiber.current; fs.readFile('input.txt', 'UTF-8', resume_cb(fiber)); var input_res = yield(); if (input_res[0]) { error_handler(input_res[0]); return; } fs.writeFile('output.txt', input_res[1], 'UTF-8', resume_cb(fiber)); var output_res = yield(); if (output_res[0]) { error_handler(output_res[0]); return; } // 処理が続く... }; Fiber(main).run();
上記は,非同期 API を呼び出した後すぐに yield してイベントループに戻り,コールバック (resume_cb が返す function) が呼ばれたタイミングで Fiber を再開しています.
こんな使い方もできるかな,といった感じですが.
おわりに
今回は fiber を使った非同期処理の記述を取り上げました.fiber は ruby でも 1.9 から導入されていますね (最近脚光を浴びているのでしょうか?).
node.js では node-fibers を利用することで Fiber が利用可能となります.これによりコールバックネストをいくぶんか軽減することができました.
次回で node.js の control-flow あたりをまとめられたら良いですね...(できるの?)
*1:ですよね?
*2:[http://en.wikipedia.org/wiki/Fiber_(computer_science):title=fiber] は軽量スレッドと呼ばれるものです.通常のスレッドはカーネルが実行をスケジューリングしますが,fiber は明示的にスケジューリングする必要があります (協調的マルチタスクと呼ばれます.明示的に CPU リソースを明け渡さないといけないアレです.).[http://en.wikipedia.org/wiki/Coroutine:title=coroutine] は,おおざっぱに言ってしまえば処理を途中で中断/再開 (suspend/resume) できるサブルーチンです.言語レベルの機能である coroutine を実装するために fiber が利用されます.