突破してみる? / Why don't you get over?

ソフトウェアエンジニアの備忘録 / Memorandum for software engineers

fibersを使って同期しよう

toppa.hatenablog.com

walkとfibersのインストール

今回はnode.jsとCoffeeScriptで遊んでみましょう。

walkとfibersをインストールします。

$ mkdir walk_fibers
$ cd walk_fibres
$ npm install walk fibers
$

walkによる非同期ディレクトリ走査

walkは、非同期にディレクトリを再帰走査してファイルを処理するライブラリです。非同期なので速度を稼ぐことができるでしょう。

  • walk.coffee
# node.js modules
Path  = require('path')

# npm modules
Walk  = require('walk')

class Traverse
  constructor: -> @traverse_async('.')

  traverse_async: (root_dir) =>
    list = []

    # *1_1: Start directory traverse.
    walk = Walk.walk(root_dir)

    # *1_2: Callback on file stat-ed.
    walk.on 'file', (root, stat, next) =>
      filename = Path.join(root, stat.name)
      list.push(filename)
      next()

    # *1_3: Callback on walk ended.
    walk.on 'end', =>
      list.sort()

      # *1_4: Do something with list *asyncronously* in the walk callback.
      console.log(list)

new Traverse

walkの動作

まず*1_1ディレクトリの走査が始まります。
そして*1_2でファイルがstatされると呼び出されるコールバックを登録します。
statされるたびに*1_2が呼び出され、配列にファイルパスを追加していきます。
ディレクトリの走査が終了するときに呼び出されるのが*1_3のコールバックです。
ディレクトリの走査が終了したら配列をソートします。
そして*1_4でlistを得てコールバック内で出力します。

実行はこうなります。

$ coffee walk.coffee
[ 'node_modules/fibers/.npmignore',
  'node_modules/fibers/LICENSE',
  'node_modules/fibers/README.md',
  'node_modules/fibers/bin/.npmignore',
  'node_modules/fibers/bin/darwin-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/darwin-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.6/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.14/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.6/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.14/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.6/fibers.node',
  ...
  'walk.coffee',
  'walk_fibers.coffee' ]
$

fibersによる同期化

今度はfibersも使います。ディレクトリ走査の非同期処理を待ち受けることができます。

  • walk_fibers.coffee
# node.js modules
Path  = require('path')

# npm modules
Walk  = require('walk')
Fiber = require('fibers')

class Traverse
  constructor: ->
    # *2_1: Register @main_procedure as the main thread and run the main thread.
    Fiber(@main_procedure).run()

  main_procedure: () =>
    list = @traverse('.')

    # *2_6: Do something with list *syncronously* in the main procedure.
    console.log(list)

  traverse: (root_dir) =>
    list = []

    # *2_2: Get the main thread for later at *2_4.
    main_thread = Fiber.current

    # Start directory traverse.
    walk = Walk.walk(root_dir)

    # Callback on file stat-ed.
    walk.on 'file', (root, stat, next) =>
      filename = Path.join(root, stat.name)
      list.push(filename)
      next()

    # Callback on walk ended.
    walk.on 'end', =>
      list.sort()

      # *2_4: Resume back the the main thread at *2_5.
      main_thread.run(list)

    # *2_3: Yield the main thread to async walk procedure
    list = Fiber.yield()
    # *2_5: Will be resumed here.
    return list

new Traverse

fibersの動作

まず*2_1のところでmain_procedure()をメインスレッドとして登録して走らせます。
*2_2でメインスレッドのオブジェクトを取得します。*2_4でこのオブジェクトを利用するための下準備です。
travase()で非同期的にwalkを処理します。
非同期処理の間にメインスレッドに制御が戻りますが、*2_3でFiber.yield()することでメインスレッドの処理を明け渡し、非同期処理が終了するのを待ちます。
非同期処理が終わると*2_4でwalk終了関数からメインスレッドを復帰させます。

メインスレッドが同期され*2_5に移りtraverse()を抜けると、*2_6でmain_procedure()に戻り、listを使ってその後の処理を行うことができるようになります。

実行はこうなります。

$ coffee walk_fibers.coffee
[ 'node_modules/fibers/.npmignore',
  'node_modules/fibers/LICENSE',
  'node_modules/fibers/README.md',
  'node_modules/fibers/bin/.npmignore',
  'node_modules/fibers/bin/darwin-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/darwin-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/darwin-x64-v8-3.6/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.14/fibers.node',
  'node_modules/fibers/bin/linux-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/linux-x64-v8-3.6/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.11/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.14/fibers.node',
  'node_modules/fibers/bin/win32-ia32-v8-3.6/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.11/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.14/fibers.node',
  'node_modules/fibers/bin/win32-x64-v8-3.6/fibers.node',
  ...
  'walk.coffee',
  'walk_fibers.coffee' ]
$

fibersの利点

main_procedure()でtraverse()を実行した後に*2_6以降の同じ階層内で更に処理を続けられるコードになっています。
walkの非同期処理の後の手続きをwalk終了コールバック内じゃなくて、メイン処理で行えるところが味噌です。