LoginSignup
18
1

More than 1 year has passed since last update.

[Elixir]外部プロセスを管理する方針を紹介

Last updated at Posted at 2022-12-24

この記事はElixir Advent Calendar2022-4の24日目です。
執筆時、新型コロナにかかっていたのでコード少なめです m(__)m


開発環境

  • Windows 10 + WSL2 + Ubuntu 22.04
  • Elixir 1.13.4
  • Erlang/OTP 25

対象者

  • Elixirの外側にある*.exeやコマンドを呼び出したい
  • 呼び出しの結果を保持しながら同じコマンドを呼び出したい
  • 重たい外部処理をスマートに呼び出したい

基本

System.cmd

引数が1つの外部処理を呼び出すときに使います。

iex> System.cmd("echo", [hello])
{"hello\n", 0}

:os.cmd

System.cmdだと引数が複数あるときに記述が少しややこしくなります。
せっかく書いたのに動かないこともあります。
そんなときに:os.cmdが便利です。

iex> :os.cmd("rm -rf hello.txt")
[]

発展

重たい処理の実行終了と確認

外部処理が重たいとき、コマンドを実行するとそのまま制御が返ってこなくなってしまいます。
私の場合は再生時間が長い動画の変換や抽出処理で体験しました。

下記はFFmpegで動画の音声を取得するコマンド例です。

iex> :os.cmd("ffmpeg -i inputfile.mp4 outputfile.mp3")
...

制御を待っている間プログラムが止まってしまいますので「コマンドを打って終わるまでは別のタスクをして、終わったら本来のタスクに戻る」という動きをしたい。
こういうときのためか、System.cmdPortモジュールを使用しているようです。
Portは下記のように使います。

iex> path = System.find_executable("ffmpeg")
iex> port = Port.open({:spawn_executable, path}, [:binary, :exit_status, {args: ["-i", "inputfile.mp4", "output.mp3"]}])

オプションにexit_statusを指定することでプログラムの終了通知を受け取ることができるようになります。
通知はPort.monitorを実行したプロセスにメッセージ送信されます。下記が受信するメッセージの例です。

iex> Port.monitor(port)
{:DOWN, ref, :port, object, reason }

連続して重たい処理

CPUに負荷がかかるような外部処理をどんどん生み出すとCPUとメモリがなくなってしまいます。
常にプロセスを監視して、終了通知が来たら次の処理をしてくれて、もしプロセスが落ちても復活してくれるそんな便利なプロセスがGenServer。

Elixir Schoolにも解説があります。
https://elixirschool.com/ja/lessons/advanced/otp_concurrency

GenServer + Portを使うと、外部に渡すデータがある限り外部処理を順次呼び出すプログラムを作ることができます。

参考記事

英語ですが、下記がGenServerとPortの参考記事になります。

まとめ

簡単ですが、外部処理を呼び出す・管理する方法について紹介しました。

  • シンプルな外部処理はSystem.cmd
  • 外部処理の引数が多いなってときは:os.cmd
  • 外部処理が重たい!ってときはPortモジュール
  • 重たい外部処理を管理したいってときは Port + GenServer
18
1
1

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
18
1