Pythonの並列処理(マルチプロセス)の基本と使い方のイメージ画像

Pythonの並列処理(マルチプロセス)の基本と使い方

  • 公開日:2019/02/02
  • 更新日:2019/06/21
  • 投稿者:n bit

Pythonで『multiprocessing モジュール』の『Process クラス』を使った並列処理(マルチプロセス)の基本と使い方について解説します。コードの実行処理を高速化させたり、プロセスを並列化させることにより同時に複数のプログラムを処理できるようになります。

  • Python

この記事は約 分で読めます。(文字)

Pythonの並列処理(マルチプロセス)の基本

Pythonの並列処理を行う方法の1つが『multiprocessing モジュール』を使う方法です。

『multiprocessing モジュール』を使うことで、複数のプロセス(サブプロセス)を生成し、マルチプロセッサーマシンの性能を最大限に引き出すことができます。また、サブプロセスを生成することで同時に複数の実行処理を行えることもメリットです。

『multiprocessing モジュール』のメリット

  • マルチプロセッサーマシンの性能を活用し高速化できる
  • サブプロセスを生成し同時に複数の処理を実行できる

今回は、『multiprocessing モジュール』の『Process クラス』による並列処理(マルチプロセス)の基本と使い方を解説します。

Note

プロセス

プロセスとは、情報処理においてプログラムの動作中のインスタンスを意味し、プログラムのコードおよび全ての変数やその他の状態を含む。

出典:プロセス - Wikipedia

並列処理(マルチプロセス)

もともと実行されているプログラムの過程(メインプロセス)があり、そこから分岐して違うプログラムの流れ(サブプロセス)を生成し同時に実行していく。

『Process クラス』による並列処理(マルチプロセス)の使い方

『Process クラス』による並列処理の使い方を説明するために下記のサンプルコードを用意しました。

サンプルコード内にはメインプロセスとサブプロセスそれぞれで実行する関数を用意し、実際に『Process クラス』で並列処理を実行させています。

コード内容の説明を一旦飛ばしまずは実行させて実行結果を確認してみましょう。

from multiprocessing import Process

from time import sleep

# メインプロセスで動かす関数
def func_1(num):
print('メインプロセスStart')
for i in range(num):
print('メインプロセス:', i)
sleep(1)
print('メインプロセスEnd')

# サブプロセスで動かす関数
def func_2(num):
print('サブプロセスStart')
for i in range(num):
print('サブプロセス:', i)
sleep(0.5)
print('サブプロセスEnd')


if __name__ == '__main__':
p = Process(target=func_2, args=(10,))
p.start()
func_1(10)

実行結果

メインプロセスStart

メインプロセス: 0
サブプロセスStart
サブプロセス: 0
サブプロセス: 1
サブプロセス: 2
メインプロセス: 1
サブプロセス: 3
サブプロセス: 4
メインプロセス: 2
サブプロセス: 5
サブプロセス: 6
メインプロセス: 3
サブプロセス: 7
サブプロセス: 8
メインプロセス: 4
サブプロセス: 9
サブプロセスEnd
メインプロセス: 5
メインプロセス: 6
メインプロセス: 7
メインプロセス: 8
メインプロセス: 9
メインプロセスEnd

 実行結果を確認するとメインプロセスとサブプロセスが同時に実行され出力されていることが確認できます。

従来通りの記述方法で『func_2』の後に『func_1』を記述すると

func_2(10)

func_1(10)

下記の実行結果のように『func_2』がすべて出力された後、『func_1』が実行されるようになります。処理が並列化されていないため1つ終わればまた次の1つが処理されるのです。

サブプロセスStart

サブプロセス: 0
サブプロセス: 1
サブプロセス: 2
サブプロセス: 3
サブプロセス: 4
サブプロセス: 5
サブプロセス: 6
サブプロセス: 7
サブプロセス: 8
サブプロセス: 9
サブプロセスEnd
メインプロセスStart
メインプロセス: 0
メインプロセス: 1
メインプロセス: 2
メインプロセス: 3
メインプロセス: 4
メインプロセス: 5
メインプロセス: 6
メインプロセス: 7
メインプロセス: 8
メインプロセス: 9
メインプロセスEnd

このように『Process クラス』を利用したことで複数のプログラムが同時に実行処理できるようになったことがお分かりになったことでしょう。

それではコード内容を1つずつ解説していきます。

『Process クラス』のインポート

まず最初に『Process クラス』を利用できるようにするため『multiprocessing モジュール』から『Process クラス』をインポートしてください。

from multiprocessing import Process

サブプロセスで利用する関数の定義

次にサブプロセスで利用する関数を定義していきます。サブプロセスを生成するには実行処理の内容を関数に定義しておかなければなりません。

def func_2(num):

print('サブプロセスStart')
for i in range(num):
print('サブプロセス:', i)
sleep(0.5)
print('サブプロセスEnd')

今回定義した関数は引数numで受け取った数だけprint関数で出力し、その間0.5秒のスリープを挟むだけの単純なものです。

サブプロセスを生成

先ほど定義した関数をサブプロセスの実行処理として生成します。記述方法は以下の通りです。

  • p = Process(target=関数名, args=(引数))

『target』にサブプロセスで実行する関数名を指定し、『args』にその関数の引数をタプルで渡します。

 p = Process(target=func_2, args=(10,))

サブプロセスを実行

生成したサブプロセスを実行しましょう。Processで生成したインスタンス『p』にstartメソッドをつけるだけです。

p.start()

これでサブプロセスはメインプロセスとは違う流れで実行されましたので、後は自由にメインプロセス内に別の実行処理を記述することで並列的に処理が進みます。サンプルコードの『func_1(10)』にあたる部分です。

サブプロセス終了まで待機

必ず必要なコードではないのでサンプルコードには記載していませんが、サブプロセスが終了するまで一時的にメインプロセスを待機させたい場合は『join』メソッドを記述します。

  • p.join()

下記は最初のサンプルコードのメインプロセスで実行するfunc_1の直前に『p.join()』を追加したものです。

from multiprocessing import Process

from time import sleep

# メインプロセスで動かす関数
def func_1(num):
print('メインプロセスStart')
for i in range(num):
print('メインプロセス:', i)
sleep(1)
print('メインプロセスEnd')

# サブプロセスで動かす関数
def func_2(num):
print('サブプロセスStart')
for i in range(num):
print('サブプロセス:', i)
sleep(0.5)
print('サブプロセスEnd')


if __name__ == '__main__':
p = Process(target=func_2, args=(10,))
p.start()
p.join()
func_1(10)

実行結果

サブプロセスStart

サブプロセス: 0
サブプロセス: 1
サブプロセス: 2
サブプロセス: 3
サブプロセス: 4
サブプロセス: 5
サブプロセス: 6
サブプロセス: 7
サブプロセス: 8
サブプロセス: 9
サブプロセスEnd
メインプロセスStart
メインプロセス: 0
メインプロセス: 1
メインプロセス: 2
メインプロセス: 3
メインプロセス: 4
メインプロセス: 5
メインプロセス: 6
メインプロセス: 7
メインプロセス: 8
メインプロセス: 9
メインプロセスEnd

メインプロセスのfunc_1が実行される手前で『p.join()』によりサブプロセスの終了まで待機になるため、実行結果を確認するとサブプロセスが全て終了後、メインプロセスが再度実行されているのが確認できます。

『p.join()』の引数に数値を指定することで、その渡した数値(秒)の間待機してからメインプロセスを再実行するようになります。下記は5秒間待機してからメインプロセスを再実行した事例です。

p.join(5)

待機秒数を指定するこちらの方法の方が実際のプログラミングでは利用シーンが多いでしょう。

Mac OS High Sierraでマルチスレッドの制限問題

ローカル環境での実行時、OSの環境が『Mac OS High Sierra』以上の場合は以下のようなエラーが出ます。

  • [__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.

これは『Mac OS High Sierra』以降、セキュリティ強化のためのマルチスレッド制限用にfork()の振る舞いが変更されたことが原因です。

そのため『Mac OS High Sierra』以降でPythonの並列化処理を利用するには、環境変数『.bash_profile』に新しいセキュリティ規則の下でマルチスレッドアプリケーションを許可する設定が必要になります。

export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

許可するには環境変数『.bash_profile』に次の1行を追加してください。

  • export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

追加方法は以下の通りです。

ターミナルを開いてnanoで環境変数『.bash_profile』を編集します。

$ nano .bash_profile

環境変数『.bash_profile』ファイルの末尾に次の行を追加してください。

export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

ファイルを保存して終了し、1度ターミナルを再起動してください。再起動後環境変数『.bash_profile』に正しく設定されているかを確認します。

$ env

以下のように1行が追加されていれば制限の許可設定は成功です。

TERM_PROGRAM=Apple_Terminal




OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES


これで『Mac OS High Sierra』以降の環境でも『multiprocessing』の『Process クラス』による並列化処理が問題なく動作します。

今日のdot

マルチプロセスによる高速化や複数のプログラムを並列的に実行するには、『multiprocessing モジュール』の『Process クラス』を利用します。

『Process クラス』の利用方法は以下の通りです。

p = Process(target=サブプロセスで実行する関数名, args=(引数))


# サブプロセス開始
p.start()

# サブプロセス終了まで待機
p.join()