Pythonは基本的に同期・シリアル実行ですが、threading
モジュールを使うと、複数のスレッドを立ち上げて非同期に処理させることができます。
Pythonは仕様上、マルチスレッドでの処理速度向上が難しいようですが、処理の内容によっては効果的な場合もあるらしいので今回はその例です。
サンプル
今回のサンプルを先に記載しておきます。ある関数(_subroutine()
)を複数のスレッドで実行するだけの単純なものです。やり方はいろいろありますが、おそらく最も簡単な例と思われるものを掲載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import threading from traceback import print_exc def _subroutine(list_): for item in list_: print("{} - {}".format(threading.current_thread().name, item)) def _split_list(src_, num_): span_ = -(-len(src_) // num_) return [src_[x:x + span_] for x in range(0, len(src_), span_)] def dispatch_func(num_threads=4): targets_ = [] for i in range(12): targets_.append("item: {}".format(i)) _threads = [threading.Thread(target=_subroutine, args=(x,)) for x in _split_list(targets_, num_threads)] # start threads and wait for thread_ in _threads: thread_.start() for thread_ in _threads: thread_.join() if __name__ == "__main__": try: dispatch_func() except: print_exc() |
実行例はこんな感じ。各スレッドの名前と引数をプリントしています。非同期なのでスレッドの立ち上がり方は不定です。
1 2 3 4 5 6 7 8 9 10 11 12 13 | > python mt_sample.py Thread-1 - item: 0 Thread-1 - item: 1 Thread-2 - item: 3 Thread-1 - item: 2 Thread-2 - item: 4 Thread-4 - item: 9 Thread-4 - item: 10 Thread-4 - item: 11 Thread-3 - item: 6 Thread-2 - item: 5 Thread-3 - item: 7 Thread-3 - item: 8 |
以下に詳細を記載しておきます。
threadingモジュールを使ったマルチスレッド処理
ここではリストを引数にして、複数のスレッドに処理を割り当てます。
呼び出す関数で処理を完結させているので返り値はありません。処理の結果が必要な場合はQueue
を使うなどして複数のスレッドから更新を受け付けるような仕組みが必要になりますが、今回は触れません。
Threadオブジェクト
基本はThread
オブジェクトに別スレッドで実行したい関数(target
)と、引数(args
)を指定して開始(.start()
メソッド)させるだけです。
また、.join()
メソッドで各スレッドの完了を明示的に待ち合わせることができます。もちろんメインスレッドで間に他の処理を記述しても構いません。
1 2 3 4 5 6 7 | _threads = [threading.Thread(target=_subroutine, args=(x,)) for x in _split_list(targets_, num_threads)] # start threads and wait for thread_ in _threads: thread_.start() for thread_ in _threads: thread_.join() |
引数(args
)はタプルですがどうやらリストも受け付けるので、引数(positional)そのものにリストを渡すために上記の変な書き方をしています。
リストの分割
マルチスレッドの使い方そのものとは関係ありませんが、サンプルには引数として渡すためにリストを分割する処理も記載しています。
元のリスト(src_
)に対し、分割数(num_
、ここではスレッド数を想定)に応じてリストのリストとして返します。
1 2 3 | def _split_list(src_, num_): span_ = -(-len(src_) // num_) return [src_[x:x + span_] for x in range(0, len(src_), span_)] |
元のリストの長さに関わらず、指定した数(以下)に分割できるので地味に便利です。
おわり。