(この記事は「言語情報 Advent Calendar 2017」 の23日目で,「ZEAM開発ログ v.0.2.0 Node.js と同じ原理の軽量コールバックスレッドを Elixir に実装してみた (背景編)」の続きです)
おしらせ
Elixirの研究に日夜励んでいるZACKYです。好評いただいた「ElixirでGPU駆動」の連載記事のまとめを,今度のfukuoka.ex#11でプレゼンテーションします! まだ若干の空席があります!
さて本題〜はじめに
「ZEAM開発ログ v.0.2.0 Node.js と同じ原理の軽量コールバックスレッドを Elixir に実装してみた (背景編)」では次のようにまとめました。
- マルチタスクを実現する方式が進化し続けています。
- Unix ではマルチプロセス方式により,メモリ管理と一体となった形でコンテキストスイッチをしていました。
- ウェブブラウザの登場とともに マルチスレッド方式が発案され,メモリ管理情報を切り替えずにコンテキストスイッチすることで効率化するようになりました。
- Node.js では,コールバック方式により,スタックメモリを確保せずに接続要求を処理する方式が発案されました。
- 私たちは Elixir に軽量コールバックスレッドを実装し,メモリ消費を抑えて Phoenix の同時セッション最大数とレイテンシを格段に改善する方式を提案します。
ではいよいよ Elixir での軽量コールバックスレッドをどう実装するか,コードを紹介したいと思います!
まずは実行例の紹介
GitHub はこちら(https://github.com/zeam-vm/zeam_callback)です。
使い方はこんな感じです。
iex(1)> pid = ZeamCallback.Receptor.new
#PID<0.111.0>
iex(2)> send(pid, {:spawn, fn(tid) -> IO.puts "foo #{tid}" end}); send(pid, {:spawn, fn(tid) -> IO.puts "bar #{tid}" end})
foo 0
bar 1
実行すると,foo 0
と bar 1
が表示されるのですが,これらはコールバックされています。番号は軽量コールバックスレッドのIDです。
使い方
-
Receptor
は,軽量コールバックスレッドへのリクエストを受け付けます。現状では次のリクエストを受け付けます。-
:spawn
: 軽量コールバックスレッドを起動します。引数として,引数を1つ持つコールバックする関数を与えます。あとでWorker
がこの関数を呼び出します。
-
近い将来,軽量コールバックスレッド同士のメッセージ通信を実装する予定です。現在,送る方の仕組み :send
リクエストはだいたい実装できました。現在,受け取る仕組みについて,試行錯誤しているところです。次のような感じでコールバックを用いてメッセージを受け取る方式がわかりやすいかなと思います。
send(pid, {:spawn, fn(tid) ->
env = %{:tid => tid}
ZeamCallback.receive(env, fn(env, message) ->
IO.puts "Thread #{env[:tid]} receives a message: #{message}"
end)
end)
実装原理
軽量コールバックスレッドの実装にあたり,Receptor
と Worker
の2つのスレッドを用意しています。
Receptor
は軽量コールバックスレッドへのリクエストを次々 Worker
に投げます。
Worker
の処理としては,最初に receive
を実行してメッセージを処理します。もし何もメッセージキューにたまっていなかったら実行キュー(env[:queue]
)から1つ取り出して,引数には軽量コールバックスレッドのIDを入れて,コールバックします。実行キューに何もなかったら10ms寝ます。
Worker
は Receptor
のプロセスIDと環境変数 env
を状態として持つプロセスです。環境変数 env
には,現状では :queue
と :threads
と :next_tid
の3つの情報を記録しています。
-
:queue
は実行キュー,すなわち次以降に実行するコールバック関数の待ち行列です。 -
:threads
は現在の軽量コールバックスレッドIDとコールバック関数の対応関係を保持しています。 -
:next_tid
は次に軽量コールバックスレッドを新規生成した時のIDが記録されています。
Worker
の持つ環境変数は,適宜 Receptor
にも :queue
リクエストで送られます。Receptor
は環境変数をバックアップとして保持します。もし Worker
が何らかの理由で無反応になってしまったときには Worker
を再起動する機能を実装する予定です。
まとめ
- 軽量コールバックスレッドを実装しました。コードは https://github.com/zeam-vm/zeam_callback に公開しています。
- 現状では軽量コールバックスレッドの起動
:spawn
を実装しています。 - 近い将来,軽量コールバックスレッド同士のメッセージ通信を実装する予定です。
- 軽量コールバックスレッドの実装にあたり,
Receptor
とWorker
という2つのスレッドを役割分担させました。
次回は軽量コールバックスレッドのメモリ消費量について評価します。お楽しみに!
明日は @twinbee さんの「ElixirのiexでとりこぼしたPIDをひろう」です。こちらもお楽しみに!
p.s.「いいね」よろしくお願いします
よろしければ,ページ左上の や のクリックをお願いしますー
ここの数字が増えると,書き手としては「ウケている」という感覚が得られ,連載を更に進化させていくモチベーションになりますので,もっとElixirネタを見たいというあなた,私たちと一緒に盛り上げてください!