bashスクリプトでマルチプロセスを手軽にやってみよう

こんにちは。サービス部の武田です。シェルスクリプトでもマルチプロセスしたいですよね。
2023.05.31

こんにちは。サービス部の武田です。

システムのタスクや作業マシンでの処理の自動化など、スクリプトを書く機会は何かとあります。その中でもシェルスクリプトはランタイムのインストールが不要で、ポータビリティの優れている選択肢のひとつです(筆者の感想です)。

マルチプロセス/マルチスレッドが必要な処理では、Pythonなどプログラミング言語を使用した方が結果的に楽な場合が多いですが、諸々の事情によりシェルスクリプトで実現したい場合があります。

今回は、実行結果(戻り値)が不要な場合と必要な場合で、私がよく使用する方法をそれぞれ紹介します。

実行結果が不要な場合

実行結果が不要な場合とは、それぞれのタスクを並列で動かし、それぞれ終わったらそのまま終了する場合です。

おそらくLinuxなどシェルの勉強をした際にバックグラウンドプロセスについて学んだはずです。一番素朴なのが&を使用したこの方法です。

#!/bin/bash

function output() {
    sleep $(( $1 * 3 % 4 + 1 ))
    echo $1

    return $1
}

output 1 &
output 2 &
output 3 &
output 4 &

wait

実行結果は呼び出した順ではなく、次のように逆順になります。

4
3
2
1

またxargsを使用する方法もあります。こちらは定義した関数をxargsから直接呼び出すことができないため、呼び方に少し工夫が必要です。

#!/bin/bash

function output() {
    sleep $(( $1 * 3 % 4 + 1 ))
    echo $1

    return $1
}

export -f output

seq 1 4 | xargs -I{} -L 1 -P 4 bash -c "output {}"

実行結果が必要な場合

続いて、実行結果が必要な場合です。実行結果を得るためにはバックグラウンドで動いているプロセスの終了を待つ必要があります。これは最初のスクリプトでも使用していたwaitコマンドが利用できます。

#!/bin/bash

function output() {
    sleep $(( $1 * 3 % 4 + 1 ))
    echo $1

    return $1
}

declare -a pids=()
declare -i results=0

for n in $(seq 1 4); do
    output "$n" &
    pids+=($!)
done

for pid in ${pids[@]}; do
    wait $pid
    results+=$?
done

echo $results

waitコマンドは引数を指定しない場合、すべてのバックグラウンドプロセスの終了を待ちますが、プロセスIDを指定し、そのプロセスの終了を待つことができます。そこで、&を使用してバックグラウンドでプロセスを走らせる際に、そのプロセスIDを控えておきます(pids変数)。次に、再度for文を回しながら、各プロセスの終了を待ち、終了したら結果を取得します(ここではresultsに足し込んでいる)。

結果は次のように、各プロセスが終了したあとで、返してきた値の合計を出力します。

4
3
2
1
10

まとめ

シェルスクリプトは比較的簡単な処理に使用されることが多いです。普段から使用しているコマンドを組み合わせてスクリプトを組めるのがメリットです。一方であまりマルチプロセス/マルチスレッドを活用する機会は少ないでしょう。

シェルスクリプトで並列処理をしたくなったらぜひ試してみてください。