15
8

More than 5 years have passed since last update.

ZEAM開発ログ v.0.2.1 Node.js と同じ原理の軽量コールバックスレッドを Elixir に実装してみた (実装編)

Last updated at Posted at 2018-06-21

(この記事は「言語情報 Advent Calendar 2017」 の23日目で,「ZEAM開発ログ v.0.2.0 Node.js と同じ原理の軽量コールバックスレッドを Elixir に実装してみた (背景編)」の続きです)

「ZEAM開発ログ 目次」はこちら

おしらせ

Elixirの研究に日夜励んでいるZACKYです。好評いただいた「ElixirでGPU駆動」の連載記事のまとめを,今度のfukuoka.ex#11でプレゼンテーションします! まだ若干の空席があります!

image.png

さて本題〜はじめに

「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 0bar 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)

実装原理

軽量コールバックスレッドの実装にあたり,ReceptorWorker の2つのスレッドを用意しています。

Receptor は軽量コールバックスレッドへのリクエストを次々 Worker に投げます。

Worker の処理としては,最初に receive を実行してメッセージを処理します。もし何もメッセージキューにたまっていなかったら実行キュー(env[:queue])から1つ取り出して,引数には軽量コールバックスレッドのIDを入れて,コールバックします。実行キューに何もなかったら10ms寝ます。

WorkerReceptor のプロセス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 を実装しています。
  • 近い将来,軽量コールバックスレッド同士のメッセージ通信を実装する予定です。
  • 軽量コールバックスレッドの実装にあたり,ReceptorWorkerという2つのスレッドを役割分担させました。

次回は軽量コールバックスレッドのメモリ消費量について評価します。お楽しみに!

明日は @twinbee さんの「ElixirのiexでとりこぼしたPIDをひろう」です。こちらもお楽しみに!

p.s.「いいね」よろしくお願いします

よろしければ,ページ左上の image.pngimage.png のクリックをお願いしますー:bow:
ここの数字が増えると,書き手としては「ウケている」という感覚が得られ,連載を更に進化させていくモチベーションになりますので,もっとElixirネタを見たいというあなた,私たちと一緒に盛り上げてください!:tada:

15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8