先日、Python3でマルチスレッド処理を試してみましたが、より実用的に使うためにはスレッドの終了を待ち合わせる処理も必要になります。そこで、今回は「Python3でスレッドの待ち合わせってどうやるの?」ということを調べてみました。
join関数で終了を待機できる
Python3.6.1の公式ドキュメントによると、スレッドの終了はjoin関数を使って待つことができるらしいです。以下、ドキュメントを一部抜粋しながら進めます。
スレッドが終了するまで待機します。 このメソッドは、 join() を呼ばれたスレッドが正常終了あるいは処理されない例外によって終了するか、オプションのタイムアウトが発生するまで、メソッドの呼び出し元のスレッドをブロックします。
つまり、作成したスレッドすべてに対してjoin関数をコールすれば、それらの終了を待つことができそうです。とはいえ、作成したすべてのスレッドを覚えておくのは面倒なので、実際は以下のenumerate関数を使ってスレッドのリストを取得するのが楽そう。
現在、生存中の Thread オブジェクト全てのリストを返します。リストには、デーモンスレッド (daemonic thread)、 current_thread() の生成するダミースレッドオブジェクト、そして主スレッドが入ります。終了したスレッドとまだ開始していないスレッドは入りません。
ここで気をつけないといけないのが、enumerate関数で返るリストにはメインスレッドも入る(上記赤字部分)というところ。メインスレッドからメインスレッド(つまりは同一スレッド)に対してjoin関数をコールすると、デッドロックとなるため例外が発生します。
そこで、以下のmain_thread関数を使ってメインスレッドを取得してあげれば、メインスレッドだけjoin関数のコールを回避できそうです。
main Thread オブジェクトを返します。通常の条件では、メインスレッドはPythonインタプリタが起動したスレッドを指します。
サンプルプログラム
前回のプログラムにスレッドの待ち合わせ処理を追加しました。スレッドのリストからメインスレッドに該当するモノだけを除外し、残りに対してjoin関数で終了を待機しています。
import time
import threading
def proc():
for i in range(0,5):
time.sleep(1)
print("count", i)
if __name__ == '__main__':
th1 = threading.Thread(target=proc)
th2 = threading.Thread(target=proc)
th1.start()
th2.start()
thread_list = threading.enumerate()
thread_list.remove(threading.main_thread())
for thread in thread_list:
thread.join()
print("All thread is ended.")
実行してみた結果はこんな感じ。確かにスレッドの終了を待つことができていそうです。
count 0
count 0
...
count 4
count 4
All thread is ended.
ということで待ち合わせ処理が実装できましたが、正直あんまりスマートでないような気もします。ほんとにこれでいいのかな…(´・ω・)
Eventというスレッド間の通信処理の仕組みもあるらしいので、高度なマルチスレッドを実現するにはそっちを使うのが正解そう。ただ、今回のように単に待ち合わせをしたいだけなら、上記のようなサンプルでも事足りる気がします。
そのうち、スレッド間通信にもチャレンジしてみたいですね。
ではではノシ