[Python] multiprocessingを試す (1)

multiprocessing

multiprocessing とは、プロセスベースで並列処理を行う python の bulid-in モジュールです。

処理を並列に行えるので、処理が速くなります。

multiprocessing — プロセスベースの並列処理

同じく並列処理を行うモジュールに、threading という bulit-in モジュールもあります。

threading — スレッドベースの並列処理

プロセスとスレッドの違いは以下を参照しました。

Windows OS入門:第3回 プロセスとスレッド (1/2)

まず、私のざっくりとした理解では以下の感じです。

プロセス

プログラムの実行単位。複数のスレッドで一つのプロセスになる。

スレッド

プロセスをより細かい実行単位に分割したもの。プロセス内で複数のスレッドは、リソース(例えば変数)を共有する。

マルチプロセス

プロセスを複数コピーして、並列で実行する。それぞれのプロセスは別なので、リソースは共有されない。

マルチスレッド

一つのプロセス内で、スレッドを複数コピーして、並列で実行する。スレッドはリソースを共有できる。

GIL グローバルインタプリタロック

また、multiporcessing とmultitheading どちらを使うべきかという話では、GIL グローバルインタプリタロック という言葉が良く出てきます。

GIL とは、実行時に一つだけ thread を実行する仕組みのことです。

グローバルインタプリタロック: Global Interpreter Lock, GIL)とは、プログラミング言語インタプリタスレッドによって保持されるスレッドセーフでないコードを、他のスレッドと共有してしまうことを防ぐための排他ロックである。インタプリタのひとつのプロセスごとに必ずひとつの GIL が存在する。

出典: フリー百科事典『ウィキペディア(Wikipedia)』

PythonのGILについて簡単に調べてみました

Pythonのマルチスレッドで気を付けるべきこと

Python は、GIL を採用しているため、multithread の取扱いが難しくなっています。

そのため、性能が十分な環境であれば、基本的に、multiprocessing を使ったほうが良いようです。

multiprocessingを試す

まずは、multiprocessingを行うための関数を作成します。

引数を3乗する関数にして、普通に実行してみます。

def cube (num):
    result = num ** 3
    print(result)

if __name__ == '__main__':

    for num in range(10):
        cube(num)

# 実行結果
# 0
# 1
# 8
# 27
# 64
# 125
# 216
# 343
# 512
# 729

想定通りの結果になっています。

同じ処理を、multiprocessing で実行するためコードを書き換えます。

multiprocessing モジュールは、まずは Process のオブジェクトを作成して、続いて start() メソッドを呼び出すことで実行できます。

Process は引数として、 「target 実行する関数」 と 「args その関数の引数(タプルで書く)」 を持ちます。

import multiprocessing

def cube (num):
    result = num ** 3
    print(result)

if __name__ == '__main__':

    for num in range(10):
        cube(num)
    
    print('---multiprocessing---')

    for num in range(10):
        process = multiprocessing.Process(target=cube, args=(num, ))
        process.start()

# 実行結果
# 0
# 1
# 8
# 27
# 64
# 125
# 216
# 343
# 512
# 729
# ---multiprocessing---
# 1
# 0
# 8
# 64
# 216
# 27
# 343
# 729
# 512
# 125

multiprocessing では、複数のプロセスを並列して実行するので、処理の終了順序がバラバラになっています。

プロセスIDを取得

bulid-in モジュールの os の getppid() というメソッドで、プロセスIDを取得、確認します。

os.getppid()

import multiprocessing
import os

def cube (num):
    result = num ** 3
    pid = os.getpid()
    print(f"process ID: {pid}  result: {result}")


if __name__ == '__main__':

    for num in range(10):
        cube(num)
    
    print('---multiprocessing---')

    for num in range(10):
        process = multiprocessing.Process(target=cube, args=(num, ))
        process.start()

# 実行結果
# process ID: 4944  result: 0
# process ID: 4944  result: 1
# process ID: 4944  result: 8
# process ID: 4944  result: 27
# process ID: 4944  result: 64
# process ID: 4944  result: 125
# process ID: 4944  result: 216
# process ID: 4944  result: 343
# process ID: 4944  result: 512
# process ID: 4944  result: 729
# ---multiprocessing---
# process ID: 16828  result: 0
# process ID: 17980  result: 8
# process ID: 12088  result: 27
# process ID: 20172  result: 1
# process ID: 15368  result: 125
# process ID: 17012  result: 512
# process ID: 624  result: 64
# process ID: 18312  result: 216
# process ID: 1520  result: 343
# process ID: 18388  result: 729

singleprocessing では同じプロセスIDとなっており、multiprocessing では、プロセスIDがそれぞれ異なっています。

process の名前の取得

multiprocessing の Processオブジェクトは、それぞれを識別する名前を持ち、name という属性でアクセスできます。

name

また、 multiprocessing.current_process() で、現在のプロセスにアクセスすることができます。

multiprocessing.current_process()

multiprocessing.current_process().name により、 プロセスIDではなく、プロセスの名前で、それぞれのプロセスを識別してみます。

import multiprocessing

def cube (num):
    result = num ** 3
    name = multiprocessing.current_process().name
    print(f"process name: {name}  result: {result}")


if __name__ == '__main__':

    for num in range(10):
        cube(num)
    
    print('---multiprocessing---')

    for num in range(10):
        process = multiprocessing.Process(target=cube, args=(num, ))
        process.start()

# 実行結果
# process name: MainProcess  result: 0
# process name: MainProcess  result: 1
# process name: MainProcess  result: 8
# process name: MainProcess  result: 27
# process name: MainProcess  result: 64
# process name: MainProcess  result: 125
# process name: MainProcess  result: 216
# process name: MainProcess  result: 343
# process name: MainProcess  result: 512
# process name: MainProcess  result: 729
# ---multiprocessing---
# process name: Process-1  result: 0
# process name: Process-4  result: 27
# process name: Process-2  result: 1
# process name: Process-3  result: 8
# process name: Process-7  result: 216
# process name: Process-6  result: 125
# process name: Process-8  result: 343
# process name: Process-10  result: 729
# process name: Process-5  result: 64
# process name: Process-9  result: 512

プロセスの名前が singleprocessing ではMainProcess、multiprocessing ではそれぞれ異なる名前になっています。

以下の記事に続きます。

以下の記事の続きです。 Python の multiprocessing で lock を使ってみます。 multiprocessing の際にリソースを共有した場合、lock をしておかないと変なことになるという話です。 lock lock...