Pythonでマルチスレッド(1) Event
はじめに
マルチスレッドプログラミングってサンプル読むだけだとわかった気になるだけで,自分で使いこなせるようにならないなーって実感している今日この頃.やっぱりいろいろ自分で実験するのが一番,ということでPythonマルチスレッドのあれこれを試していきたいと思います.
まずはEventからやっていきます.
準備
Python環境は3系を想定しています. 必要なモジュールのimportと,各スレッドの動きをわかりやすくするためにロガーの設定をします. ロガーはスレッド名や時刻も出力するようにしておくとGOOD.printとロガー出力は「まぜるな禁止」です(混ぜると出力の一貫性が崩れたりするらしい).
import logging import threading from threading import Thread import queue from time import sleep logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(threadName)s: %(message)s')
Event
threading --- スレッドベースの並列処理 — Python 3.7.5 ドキュメントの説明を見てみましょう.
イベントは、あるスレッドがイベントを発信し、他のスレッドはそれを待つという、スレッド間で通信を行うための最も単純なメカニズムの一つです。
イベントオブジェクトは内部フラグを管理します。このフラグは set() メソッドで値を true に、 clear() メソッドで値を false にリセットします。 wait() メソッドはフラグが true になるまでブロックします。
この後半部分を図にしてみました.これを頭の片隅に置きながら実験していきましょう.
Event.set()
まずはEventで起動しているスレッドを止めてみたいと思います. メインスレッドで別のスレッド(thread1)を起動し,一定時間(ここでは6秒)たったら停止させるというプログラムです. thread1ではwhileループで処理を継続させます.このループを抜けない限りはthread1スレッドは動き続けます.
ループの脱出条件に使っているのがEventです.
Eventは初期状態ではFalse
ですが,セット(set()
)されるとis_set()
でTrue
を返すようになるので,これを使ってループを脱出します.Eventはメインスレッドで定義し,セットします.
def thread1(): logging.info('start') while not is_stopped.is_set(): logging.info('...working...') sleep(2) logging.info('end') if __name__ == '__main__': is_stopped = threading.Event() logging.info('is_stopped: ' + str(is_stopped.is_set())) t = Thread(target=thread1, name='thread1') t.start() sleep(6) logging.info('stopped thread1') is_stopped.set() logging.info('is_stopped: ' + str(is_stopped.is_set()))
結果
2019-11-10 22:04:41,995 MainThread: is_stopped: False # (a) 2019-11-10 22:04:41,996 thread1: start 2019-11-10 22:04:41,996 thread1: ...working... 2019-11-10 22:04:43,997 thread1: ...working... 2019-11-10 22:04:46,002 thread1: ...working... 2019-11-10 22:04:47,997 MainThread: stopped thread1 2019-11-10 22:04:47,998 MainThread: is_stopped: True # (b) 2019-11-10 22:04:48,007 thread1: end
メインスレッドにおけるEventのセットでthread1が停止したことがわかります.
また,Eventはset()
が実行されるまではis_set()
でFalse
を返し(a),実行されるとTrue
を返す(b)こともわかります.
Event.clear()
続いて,clear()
です.set()
と逆の動きになります.
下のコードではis_hard_mode
というEventを用意して,set()
とclear()
を繰り返します.
1秒おきに"...working..."か"...working so hard..."のメッセージが出力されますが,メッセージの内容がset()
とclear()
で切り替わっていることがわかると思います.
def thread1(): logging.info('start') while not is_stopped.is_set(): if is_hard_mode.is_set(): logging.info('...working so hard...') else: logging.info('...working...') sleep(1) logging.info('end') if __name__ == '__main__': is_stopped = threading.Event() is_hard_mode = threading.Event() t = Thread(target=thread1, name='thread1') t.start() sleep(3) is_hard_mode.set() sleep(2) is_hard_mode.clear() sleep(2) is_hard_mode.set() sleep(3) is_stopped.set()
結果
2019-11-10 22:57:01,789 thread1: start 2019-11-10 22:57:01,789 thread1: ...working... 2019-11-10 22:57:02,794 thread1: ...working... 2019-11-10 22:57:03,799 thread1: ...working... 2019-11-10 22:57:04,803 thread1: ...working so hard... 2019-11-10 22:57:05,807 thread1: ...working so hard... 2019-11-10 22:57:06,809 thread1: ...working... 2019-11-10 22:57:07,815 thread1: ...working... 2019-11-10 22:57:08,820 thread1: ...working so hard... 2019-11-10 22:57:09,820 thread1: ...working so hard... 2019-11-10 22:57:10,826 thread1: ...working so hard... 2019-11-10 22:57:11,829 thread1: end
Event.wait()
Eventがset()
されるまで,そこでスレッドの進行をブロック(一時停止)します.
thread1スレッドが起動されると,(a)まで処理が進みます.
ここでis_started
のwait()
が呼び出されます(b).すると,is_started
がset()
されるまでこのスレッドは待つことになります.
is_started
がset()
されているのはメインスレッドで,thread1スレッドを起動してから3秒後です.
def thread1(): logging.info('waiting') logging.info('is_started: ' + str(is_started.is_set())) # (a) is_started.wait() # (b) logging.info('is_started: ' + str(is_started.is_set())) # (d) logging.info('start') if __name__ == '__main__': logging.info('main start') is_started = threading.Event() t = Thread(target=thread1, name='thread1') t.start() sleep(3) is_started.set() # (c) t.join()
結果
結果を見てみると,(a)と(d)の間で3秒経過していることがわかります.また,is_started.is_set()
もFalse
からTrue
に変わっていることが確認できます.
2019-11-12 22:28:25,257 MainThread: main start 2019-11-12 22:28:25,257 thread1: waiting 2019-11-12 22:28:25,257 thread1: is_started: False 2019-11-12 22:28:28,259 thread1: is_started: True 2019-11-12 22:28:28,259 thread1: start