勉強の為に引用しました。
http://tips.hecomi.com/entry/20121021/1350819390
はじめに
前回(Node.js で C++ アドオンから EventEmitter のイベントリスナを呼ぶ - 凹みTips)、C++ のネイティブモジュールから EventEmitter を利用して JavaScript 側で定義したイベントリスナを呼ぶ方法を紹介しました。ただ Node.js はシングルスレッドベースの非同期処理を行っているため、このイベントリスナを呼ぶ過程のどこかで重い処理を行うと、全体の処理がブロックされてしまいます。また、これを避けるために、子スレッドを作成して処理が終了したら JavaScript のイベントリスナを呼ぶ、としようとしても、別スレッド内から Node.js の走るスレッドの v8 の世界へ直接アクセスすることが出来ないため、イベントリスナを呼ぶことができません*1。
そこで、libeio / libev に代わって Node.js のコアとして置き換わりつつある libuv の機能であるuv_queue_work を用いた、別スレッドでの非同期処理の実現方法を紹介します。
そこで、libeio / libev に代わって Node.js のコアとして置き換わりつつある libuv の機能であるuv_queue_work を用いた、別スレッドでの非同期処理の実現方法を紹介します。
参考
- node.js C addon queueing by uv_queue_work - Stack Overflow
- How to migrate from eio_custom to uv_queue_work · joyent/node Wiki · GitHub
- joyent/libuv · GitHub
(2012/10/27 追加)
下記サイトのスライドがとても分かりやすいです。
下記サイトのスライドがとても分かりやすいです。
コード
第1引数で受け取った数字を2倍して第2引数のコールバックの引数として1秒後に呼ぶ、といった簡単な内容になっています。この「2倍して1秒間待ってる」ところが非同期処理で行われています。
async.js
addon.cc
binding.gyp
ビルド & 実行
解説
イベントループへは現在回っているもの( uv_default_loop() )を与えておきます。やりとり用のデータは uv_work_t* 型で、uv.h で定義されている(uv.h:L352)ように、uv_work_t 型は void* 型の data をメンバに持っています。なので、ここにユーザが定義した適当なデータのポインタを突っ込んで置きます。そして、この uv_work_t* 型の引数を受け取る非同期処理を行う関数(ここでは AsyncWork)、それが終わった後のコールバック用関数(AsyncAfter)を与えます。この AsyncWork や AsyncAfter 内で uv_work_t* 型の引数 req の data を元々の型にキャストして色々な処理を行います。
実行結果を見ても分かる通り、AsyncWork 内だけ別スレッドでの実行となっています。なので、この中では v8 の世界にアクセス出来ないので、処理に必要なデータは予め data の中に突っ込んで置くようにします。
実行結果を見ても分かる通り、AsyncWork 内だけ別スレッドでの実行となっています。なので、この中では v8 の世界にアクセス出来ないので、処理に必要なデータは予め data の中に突っ込んで置くようにします。
別スレッドからの実行
現在は、メインスレッドから uv_queue_work を実行していますが、これを利用すれば別スレッドで動いている場所から同様に uv_queue_work を実行して非同期処理を行うこともできます。イメージとしては、別スレッドでコールバックが呼ばれたとき、そのタイミングで JavaScript のイベントリスナを呼びたい、といった状況です。
addon.cc
結果
最終的な AsyncAfter は元々のスレッドに戻ってくるのでそのタイミングでイベントリスナやコールバックを呼んであげれば OK です。
おわりに
元々は音声認識ライブラリ Julius の発話開始コールバックを JavaScript 側に通知したくて調べたのですが、思ったより時間がかかってしまいました。libuv は触りたてなので、間違っている場所があるかもしれませんが、その際は教えていただけると助かります。
0 件のコメント:
コメントを投稿