マルチタスクの概念
マルチタスクとは、複数のタスクを同時に実行する概念です。現代のオペレーティングシステムはすべてマルチタスクに対応しており、複数のアプリケーションを同時に実行できます。
マルチタスクの紹介
マルチタスクは、プログラムの実行効率を高めるために利用されます。マルチタスクには「並行 (Concurrency)」と「並列 (Parallelism)」の2つの実行形態があります。
並行 (Concurrency)
一定期間内にタスクを交互に実行する方式です。単一コアのCPUでマルチタスクを実現する場合、オペレーティングシステムは各ソフトウェアにCPU時間を割り当て、交互に実行させます。CPUの処理速度が非常に速いため、あたかもすべてのソフトウェアが同時に実行されているかのように見えます。
並列 (Parallelism)
マルチコアCPUでマルチタスクを実現する場合、オペレーティングシステムは各CPUコアに個別のソフトウェアを割り当て、それらを同時に実行します。これにより、複数のタスクが真に同時に実行されます。
プロセスの紹介
Pythonでマルチタスクを実現するには、プロセスを利用します。プロセスは、オペレーティングシステムがリソースを割り当てる基本的な単位です。プロセスが起動されると、オペレーティングシステムはそのプロセスが実行されるために必要なリソース(メモリなど)を割り当てます。
例えるなら、会社がプロセスであり、従業員がスレッドに相当します。会社(プロセス)は、コンピュータやデスクなどのリソースを提供し、従業員(スレッド)が実際の作業を行います。
注意点として、1つのプログラムが実行されると、少なくとも1つのプロセスが生成されます。1つのプロセスには、デフォルトで1つのスレッドが含まれます。プロセスは複数のスレッドを作成できますが、スレッドはプロセスに依存しており、プロセスなしでは存在できません。
プロセスの役割
マルチプロセスを利用することで、各プロセスが独立した事業を行う会社のように、それぞれのタスクを独立して実行できます。
プロセスの使用方法
- モジュールのインポート:
multiprocessingモジュールをインポートします。 - タスク関数の定義: プロセスで実行したい処理を関数として定義します。
- 子プロセスの作成:
multiprocessing.Processクラスを使用して子プロセスを作成します。 - 子プロセスの起動: 作成した子プロセスを
start()メソッドで起動します。
multiprocessing.Process クラス
Process クラスは、子プロセスを作成するために使用されます。主な引数とメソッドは以下の通りです。
target: プロセスで実行する関数を指定します。name: プロセスに名前を付けます(任意)。args: 関数に渡す引数をタプル形式で指定します。kwargs: 関数に渡す引数を辞書形式で指定します。start(): プロセスを起動します。join(): 子プロセスが終了するまで待機します。terminate(): 子プロセスを強制終了します。
マルチプロセスによるマルチタスクのコード例
import multiprocessing
import time
def dance():
for _ in range(5):
print("Dancing...")
time.sleep(0.2)
def sing():
for _ in range(5):
print("Singing...")
time.sleep(0.2)
if __name__ == '__main__':
# ダンスを実行するプロセスを作成
dance_process = multiprocessing.Process(target=dance, name="Dancer")
# 歌を歌うプロセスを作成
sing_process = multiprocessing.Process(target=sing)
# プロセスを起動
dance_process.start()
sing_process.start()
現在のプロセス情報を取得・設定する
os モジュールと multiprocessing モジュールを使用して、現在のプロセスIDや親プロセスID、および現在のプロセスオブジェクトを取得できます。
os.getpid(): 現在のプロセスIDを取得します。os.getppid(): 親プロセスIDを取得します。multiprocessing.current_process(): 現在のプロセスオブジェクトを取得します。
プロセスID取得のコード例
import multiprocessing
import time
import os
def dance():
print(f"Dance Process ID: {os.getpid()}")
print(f"Dance Process Object: {multiprocessing.current_process()}")
for _ in range(5):
print("Dancing...")
time.sleep(0.2)
def sing():
print(f"Sing Process ID: {os.getpid()}")
print(f"Sing Process Object: {multiprocessing.current_process()}")
for _ in range(5):
print("Singing...")
time.sleep(0.2)
if __name__ == '__main__':
print(f"Main Process ID: {os.getpid()}")
print(f"Main Process Object: {multiprocessing.current_process()}")
dance_process = multiprocessing.Process(target=dance, name="Dancer")
sing_process = multiprocessing.Process(target=sing)
dance_process.start()
sing_process.start()
親プロセスID取得のコード例
import multiprocessing
import time
import os
def dance():
print(f"Dance Process ID: {os.getpid()}")
print(f"Dance Parent Process ID: {os.getppid()}")
for _ in range(5):
print("Dancing...")
time.sleep(0.2)
def sing():
print(f"Sing Process ID: {os.getpid()}")
print(f"Sing Parent Process ID: {os.getppid()}")
for _ in range(5):
print("Singing...")
time.sleep(0.2)
if __name__ == '__main__':
print(f"Main Process ID: {os.getpid()}")
dance_process = multiprocessing.Process(target=dance, name="Dancer")
sing_process = multiprocessing.Process(target=sing)
dance_process.start()
sing_process.start()
プロセスに引数を渡す
Process クラスの args または kwargs 引数を使用して、プロセスで実行される関数に引数を渡すことができます。
args: 引数をタプルで指定します。タプルの要素の順序が関数の引数の順序と一致している必要があります。kwargs: 引数を辞書で指定します。辞書のキーが関数引数名と一致している必要があります。
args を使用したコード例
import multiprocessing
import time
def execute_task(count):
for _ in range(count):
print("Task executing...")
time.sleep(0.2)
print("Task finished.")
if __name__ == '__main__':
# count=5 をタプルで渡す
process_with_args = multiprocessing.Process(target=execute_task, args=(5,))
process_with_args.start()
kwargs を使用したコード例
import multiprocessing
import time
def execute_task(count):
for _ in range(count):
print("Task executing...")
time.sleep(0.2)
print("Task finished.")
if __name__ == '__main__':
# count=3 を辞書で渡す
process_with_kwargs = multiprocessing.Process(target=execute_task, kwargs={"count": 3})
process_with_kwargs.start()
プロセス間でのグローバル変数の共有
プロセスはそれぞれ独立したメモリ空間を持つため、デフォルトではグローバル変数を共有しません。子プロセスは親プロセスのリソースのコピーを受け取りますが、その後の変更は共有されません。
import multiprocessing
import time
global_list = []
def add_data():
for i in range(5):
global_list.append(i)
print(f"Adding: {i}")
time.sleep(0.2)
print(f"add_data function: {global_list}")
def read_data():
print(f"read_data function: {global_list}")
if __name__ == '__main__':
add_process = multiprocessing.Process(target=add_data)
read_process = multiprocessing.Process(target=read_data)
add_process.start()
# add_process が終了するのを待つ
add_process.join()
read_process.start()
print(f"Main process: {global_list}")
上記コードの実行結果では、main process での global_list は空のままです。これは、add_data 関数内で変更された global_list が main プロセスには反映されていないためです。
メインプロセスの終了と子プロセスの待機
デフォルトでは、メインプロセスはすべての子プロセスが終了するのを待ってから終了します。子プロセスの実行中にメインプロセスを終了させたい場合は、子プロセスをデーモンプロセスとして設定するか、明示的に終了させる必要があります。
デーモンプロセス
子プロセスをデーモンプロセスとして設定すると、メインプロセスが終了したときに子プロセスも自動的に終了します。これは、子プロセスの daemon 属性を True に設定することで行います。
import multiprocessing
import time
def task():
for _ in range(10):
print("Task running...")
time.sleep(0.2)
if __name__ == '__main__':
sub_process = multiprocessing.Process(target=task)
# デーモンプロセスに設定
sub_process.daemon = True
sub_process.start()
time.sleep(0.5) # メインプロセスを0.5秒待機させる
print("Main process finished.")
# メインプロセスが終了すると、デーモンプロセスであるsub_processも終了する
子プロセスの明示的な終了
terminate() メソッドを使用して、子プロセスを明示的に終了させることもできます。
import multiprocessing
import time
def task():
for _ in range(10):
print("Task running...")
time.sleep(0.2)
if __name__ == '__main__':
sub_process = multiprocessing.Process(target=task)
sub_process.start()
time.sleep(0.5) # メインプロセスを0.5秒待機させる
print("Main process terminating child process.")
sub_process.terminate() # 子プロセスを強制終了
print("Main process finished.")
これらの方法により、プロセスのライフサイクルをより柔軟に管理できます。