🍣

Python multithreading and multiprocessing

2024/08/23に公開2

背景

  • マルチスレッドで動作しているプロセスを並列処理できるようにしたい。

multithreading

pythonでマルチスレッドを実装する場合、asyncioを用いて以下のように実装できるだろう。

import asyncio


class AsyncTask:
    async def run(self):
        print("async task!")
        await asyncio.sleep(0.1)


if __name__ == "__main__":
    asyncio.run(AsyncTask().run())

multiprocessing

pythonでマルチプロセス(並列処理)を実装する場合、以下のように実装できる(いくつかパターンがある)

import os
import sys
from multiprocessing import Pool
from typing import List


class Task:
    def run(self):
        print(f"DummyAsyncTask: Hello from process(id={os.getpid()})")


class ParallelTaskExecuter:
    def __init__(self, num_processes: int):
        self.num_processes = num_processes

    def execute_tasks(self, tasks: List[Task]):
        with Pool(processes=self.num_processes) as process:
            for task in tasks:
                process.apply(task.run)


if __name__ == "__main__":
    executor = ParallelTaskExecuter(2)
    executor.execute_tasks([Task() for _ in range(2)])

multiprorocessing and multithreading

次に考えるのが、非同期処理を並列に処理することだ。非同期タスクを並列に処理する場合を考える。

import asyncio
import os
import sys
from multiprocessing import Pool
from typing import List


class AsyncTask:
    def run(self):
        asyncio.run(self.__run())

    async def __run(self):
        print(f"DummyAsyncTask: Hello from process(id={os.getpid()})")
        await asyncio.sleep(0.1)
        sys.stdout.flush()


class ParallelAsyncTaskExecutor:
    def __init__(self, num_processes: int):
        self.num_processes = num_processes

    def execute_tasks(self, tasks: List[AsyncTask]):
        with Pool(processes=self.num_processes) as process:
            res = [
                process.apply_async(
                    task.run,
                )
                for task in tasks
            ]
            for r in res:
                r.wait()


if __name__ == "__main__":
    execturor = ParallelAsyncTaskExecutor(2)
    execturor.execute_tasks([AsyncTask() for _ in range(2)])

各プロセス内で非同期処理を実行させるようにする必要がある。

これに気づくのにかなり時間を使ってしまった。

GitHubで編集を提案

Discussion

まちゅけんまちゅけん

通りすがりの指摘みたいで申し訳ないのですが、参考までに共有させていただきます。
コルーチンと asyncio による同時実行は、基本的にはマルチスレッドではありません。 シングルスレッドでの非同期処理です。 以前のドキュメントでは明記されていました。

シングルスレッド処理を並行で実行するコードを作成するためのインフラストラクチャを提供します。

https://docs.python.org/ja/3.6/library/asyncio.html
「asyncio シングルスレッド」などで検索するとご参考になるかもしれません。

またこちらはただの参考情報ですが、マルチプロセスの処理は標準ライブラリ concurrent.futures.ProcessPoolExecutor というのもあります。 これは multiprocessing を使いやすくラップしてくれてるクラスなので、要件によっては便利に使えるかもしれません。
https://docs.python.org/ja/3/library/concurrent.futures.html#processpoolexecutor

tsugumi-systsugumi-sys

指摘ありがとうございます!
asyncioはシングルスレッドなんですね。そういえばイベントループが使われているのもシングルスレッドで非同期処理をやっているからなのか。