🐕

【Python】 並列処理を理解しよう  【threadingの使い方 01】

2023/06/07に公開

はじめに

並列処理をしたいです。

複数のCPUコアまたはマルチスレッドを活用することで、一度に複数のタスクを同時に処理できます。これにより、タスクの合計実行時間が短縮され、アプリケーションのパフォーマンスが向上します。

今回はPythonで並列処理をする時の基本を紹介します。

【おさらい】 Pythonの処理の順番は?

読み飛ばしてもらっても構いません。

Pythonでは、処理をコードの上から順に実行していきます。
どのプログラミングでも基本は同じだと思います。

たとえば次のような感じ。

基本
print("春はあけぼの。")
print("やうやう白くなりゆく山ぎは、")
print("すこしあかりて、")
print("紫だちたる雲のほそくたなびきたる。 ")
 結果
春はあけぼの。
やうやう白くなりゆく山ぎは、
すこしあかりて、
紫だちたる雲のほそくたなびきたる。 

上から順に処理されています。
至って普通のコードです。

しかし、並列処理のコードを書くと、これが普通ではなくなります。

並列処理の基本

並列処理を書いていきます。

① threadingライブラリを使います

Pythonで並列処理をしたい時、「 threading 」というライブラリを使用します。
標準ライブラリなので、pip install XXはしなくてOKです。
次のように使用します。

今いるスレッドを確認
import threading

print(threading.currentThread().getName())
 結果
MainThread

上記のコードは、「 いま動作しているスレッドを確認する 」というコードです。
ツラツラと書いていますが、コピペでOKです。

結果から、「 MainThread 」というスレッドにいることがわかりました。
イメージとしては、下図のようなメインとなる線の上にいる感じです。

さて、もう少し詳しくみていきましょう。

② スレッドを増やしてみよう

スレッドを増やしてみる
import threading
import time

# うどんを茹でる関数
def boil_udon():
    print("  ◆スレッド:", threading.currentThread().getName())

    print('  うどんを茹でます。')
    time.sleep(3)
    print('  うどんが茹であがりました。')

# メイン
if __name__ == "__main__":
    print("◆スレッド:", threading.currentThread().getName())

    print('うどんを作ります。')

    # スレッドを作る
    thread1 = threading.Thread(target=boil_udon)

    # スレッドの処理を開始
    thread1.start()

    # スレッドの処理を待つ
    thread1.join()

    print('盛り付けます。')
    print('うどんができました。')

※こちらのコードを参考にしました。わかりやすいです。↓
https://qiita.com/tchnkmr/items/b05f321fa315bbce4f77

たくさんコードを追加しましたが、コピペでOKです。
実行すると次のような結果が返ってくるはずです。

結果
◆スレッド: MainThread
うどんを作ります。
  ◆スレッド: Thread-1
  うどんを茹でます。
  うどんが茹であがりました。
盛り付けます。
うどんができました。

メインのスレッドの処理がされてから、別のスレッド(Thread-1)の処理が実行されています。

このコードが何をしているか、簡単に解説します。

まず、「 threading.Thread() 」でスレッドを追加することができます。名前を指定しなければ、連番で「 Thread-1 」のようにが自動で命名されます。引数のtargetに並列処理したい関数を渡します。

そして、.start()を実行することで、スレッドが開始して、boil_udon内の処理が進んでいきます。この時、MainThreadと並行して処理されていることに注意してください。

.join()を実行することで、Thread-1の処理が終わるまで、MainThreadが待ってくれます。Thread-1の処理が終わると、次の処理へと進みます。

イメージにすると、下図のような感じです。

③ スレッドをもう1つ追加してみる

上記のコードで、うどんを茹でることができました。
このコードに、うどんのツユを作る関数を追加して、うどんを茹でている間にツユを作りましょう。

うどんを茹でるのと、ツユを作るのを同時に行う、つまり並列処理です。

スレッドを増やしてみる

import threading
import time

# うどんを茹でる関数
def boil_udon():
    print("  ◆スレッド:", threading.currentThread().getName())

    print('  うどんを茹でます。')
    time.sleep(3)
    print('  うどんが茹であがりました。')

# ツユを作る関数
def make_tuyu():
    print("  ◆スレッド:", threading.currentThread().getName())

    print('  ツユをつくります。')
    time.sleep(2)
    print('  ツユができました。')

# メイン
if __name__ == "__main__":
    print("◆スレッド:", threading.currentThread().getName())

    print('うどんを作ります。')

    # スレッドを作る
    thread1 = threading.Thread(target=boil_udon)
    thread2 = threading.Thread(target=make_tuyu)

    # スレッドの処理を開始
    thread1.start()
    thread2.start()

    # スレッドの処理を待つ
    thread1.join()
    thread2.join()

    print('盛り付けます。')
    print('うどんができました。')

結果は次のようになります。

◆スレッド: MainThread
うどんを作ります。
  ◆スレッド: Thread-1
  うどんを茹でます。
  ◆スレッド: Thread-2
  ツユをつくります。
  ツユができました。
  うどんが茹であがりました。
盛り付けます。
うどんができました。

Thread-2の処理が追加され、並列に動作していることがわかります。
これで、うどんを茹でている間にツユを作ることができました。

図にすると下図のようなイメージです。

まとめ

  • threading.Threadでスレッドを追加できる
  • 引数にスレッドに実行させたい関数を渡す
  • .start()メソッドで、スレッドが開始される
  • .join()メソッドで、スレッドが終わるまで待つ

参考

https://qiita.com/tchnkmr/items/b05f321fa315bbce4f77

Discussion