非同期通信ことはじめ

Pythonで書かれたVim用shellフロントエンドvimshというソースを読んでます。かなり便利なんですが、古いしバグがあるので、修正を加えたり加筆を加えながら使ってます。どうやら補完も出来そうです。

vimshの凄いのは、シェルを起動していても、他の作業が出来る事。Vimはマルチスレッドを採用していません。Vim上でスレッドを使うと一瞬で落ちます。僕の最大の疑問は、「マルチスレッドを採用していないVimで何故マルチスレッドのような処理が出来るのか」

さて、catしてみましょう。

% cat
_ ← 入力待

通常getchar(3)を呼んだらユーザーの入力があるまで待機します。OSに処理を渡すからです。しかし、vimshでは、catのような待機状態でも他の作業を遂行することが出来ます。

むぅ。謎です謎。

vimshのソースを解析して答えが見えてきました。答えは「非同期通信」です。

Rubyで書いてみます。

require 'pty'

class Test
  def self.hello
    r,w,pid = PTY.getpty("cat")
    w.puts "非同期通信出来るかな?"
    print r.sysread(2000)

    w.puts "もう一度・・・"
    print r.sysread(2000)

  end
end

Test.hello

forkの処理を簡単にするためにptyを使ってます。

sysread(2000)で2000文字読み出してみます。どうなるかな。

実行してみます。

非同期通信出来るかな?
もう一度・・・

おぉ、次のが読めてる。read(2)は待機しない。

つまり、

非同期通信出来るかな?
間になんか処理を加える。
もう一度・・・

なんて事が出来る訳。vimshの謎が解けました。システムコールread(2)はスゲー。

vimshではシステムコールselect(2)も使っていますselect(2)の謎も解いていきたいと思います。

select(2)

select(2)についても調べてみます。

実際ソースを書いた方が早そうだ。

rrequire 'pty'

class Test
  def self.hello
    r,w,pid = PTY.getpty("cat")
    5.times {|i| w.puts "#{i} times" }
    while (true)
      rs, ws, es = IO.select([r], [], [], 0.01)
      if rs.nil?
        return "timeout"
      else
        rs.each do |f|
          print f.sysread(20)
        end
      end
    end
  end
end

p Test.hello

実行してみると

0 times
1 times
2 times
3 times
4 times
0 times
1 times
2 times
3 times
4 times
"timeout"

まず、入力0〜4まで入力 → 0〜4まで出力 → 0.01秒待っても応答が無いので、処理を中断し"timeout"を出力。

「入力を待って入力が無ければ中断」

select(2)スゲー。

read(2)+select(2)によってスレッドが無いアプリケーションにも非同期通信を搭載することが出来る!!

謎がまたひとつ解けた。

詳しくは、select_tut(2)に書いてある。詳しすぎ。

プロセスをkillしてないデス。注意。

しばらく触ってみると

  • 「非同期」だとコマンドを大量に送りつける事が出来てしまうので、タイミングを取るのが難しい。
  • マルチスレッドではないので読み出してる間は操作が止まる(同期よりは断然軽い)。
  • タイマを持って読み出すと必要が無くても読みにいくので重くなる(vimshではデフォルトでoffになってる)

非同期は便利だけど、問題点も多い。