並行処理と並列処理に関するメモ
プロセス、スレッド、タスクについて
タスクはスレッドによって処理される。
スレッドはプロセスの中に最低1つある。
プロセスはOSが管理・実行する。
このことを前提に、プロセスとスレッドの関係についてメモする。
あるアプリケーションを起動したとき、OSにとってはプロセスが1つ起動している。
あるアプリケーションは複数の処理を行うことができるので、1個のプロセスには複数のスレッドが動いている。
1つのプロセスには最低でも1つのスレッドがある。
Pythonで複数のタスクを同時に処理するには、以下の2通りの方法がある。
・複数のプロセスを立ち上げ、それぞれ1つずつスレッドを立ち上げる
・1つのプロセスの中で複数のスレッドを立ち上げる
複数のプロセスで複数のスレッドを立ち上げることもできるが複雑になる。
並行処理と並列処理の違い
並行処理
1つのプロセッサで複数のスレッドを立ち上げてタスクを処理する。
ある時点では1つのスレッドの処理しか行っていないが、複数のスレッドを高速に切り替えながら行う。
そのため複数のスレッドの処理を同時に行っているように見える。
並行処理では1つのプロセッサ(CPUコア)に割り当てられたリソース(メモリなど)を複数のスレッドが共有している。
そのため、あるスレッドでのタスクの処理によって、別スレッドのタスクで保持していた情報などが意図せず書き換えられる可能性がある。
並行処理ではこの点を意識しておく必要がある。
並列処理
複数のプロセッサで、それぞれ最低1つのスレッドを立ち上げてタスクを処理する。
マルチコア(プロセッサを複数持つ)のコンピュータでしか行うことができないが、ある時点で複数の処理を同時に行っている。
それぞれのプロセッサ(CPUコア)に対して最低1つ以上のスレッドを作成してタスクを行う。
並行処理とは違って一定時間で別のスレッドに切りかえるわけではなく、完全に同時に別々のタスクを処理している。
複数のプロセッサそれぞれに複数のスレッドを作成して、並行処理を並列処理するようなこともできると思われるが複雑になりすぎる。
threadingでの並行処理の実装
逐次処理
まずは逐次処理。並行処理・並列処理のどちらも使わず、上から順番に処理を行う。
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 |
import time def task1(): print("task1 開始\n") time.sleep(5) print("task1 終了\n") def task2(): print("task2 開始\n") time.sleep(2) print("task2 終了\n") start_time = time.time() task1() task2() end_time = time.time() print(end_time - start_time) #以下は出力 #task1 開始 #task1 開始 5秒程かけて終了 #task2 開始 task1の後に開始 #task2 終了 2秒程かけて終了 |
task1とtask2を順番に処理する。
実行すると、task1を5秒程かけて処理する。
次にtask2を2秒程かけて処理する。
合計は7秒程の処理時間になる。
並行処理1
次に並行処理。1つのプロセスで複数のスレッドを立ち上げて複数のタスクを処理する。
以下はthreadingを使った並行処理。
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 31 32 33 34 35 |
import time from threading import Thread def task1(): print("task1 開始\n") time.sleep(5) print("task1 終了\n") def task2(): print("task2 開始\n") time.sleep(2) print("task2 終了\n") start_time = time.time() #スレッドを2つ用意 thread1 = Thread(target=task1) thread2 = Thread(target=task2) #各スレッドの処理を開始 thread1.start() thread2.start() #両方のスレッドの処理が終わるまで待つ thread1.join() thread2.join() end_time = time.time() print(end_time - start_time) #以下は出力 #task1 開始 #task2 開始 #task2 終了 ←task2の方が速く終わる #task1 開始 |
処理の流れは以下の通り。
task1とtask2の順に処理を開始。
task1は5秒程、task2は2秒程の処理なのでtask2の方が先に終わる。
task2が先に終わっているので、プログラム全体の終了はtask1の終了時間次第になる。
task1が5秒程かけて処理を終えるので、プログラム全体も5秒ほどで処理を終える。
threadingは組み込みモジュール。
threadingからThreadをインポートし、Threadをインスタンス化して使用している。
thread1,thread2はThreadのインスタンス。2つのスレッドを作成し、thread1,thread2という名前を付けた。
スレッドはstartメソッドで実行開始し、joinメソッドで終了を待機する。
joinメソッドは、joinメソッドを使用したスレッドが終了するまで次の処理に進むことを待機させることができる。複数のスレッドで足並みをそろえたいときに使用する。
今回の例ではthread2の方が早く終わるので、thread1の終了を待つためにthread1にjoinメソッドを付けている。
thread1のjoinメソッドをコメントアウトすると、thread2が終了したらthread1の終了を待たずに次の処理へ進んでしまう。
逆にthread2のjoinメソッドをコメントアウトしても、thread2の方が速く終わり、後からthread1が終了するのであまり意味はない。
次の処理に進む際、全てのスレッドが終わるまで待機させたい場合は、処理の終了が遅い方に対してjoinメソッドを使用する。
並行処理2
良い例ではないかもしれないがもう一例
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
import time from threading import Thread def make_rice(): print("ごはんの準備を開始\n") time.sleep(2) print("米をとぐ\n") time.sleep(3) print("炊飯器で炊く\n") time.sleep(5) print("ごはんの準備が完了\n") def make_curry(): print("カレーの準備を開始\n") time.sleep(2) print("カレーの野菜を切る\n") time.sleep(2) print("肉と野菜を炒めて煮込む\n") time.sleep(4) print("ルーを入れてさらに煮込む\n") time.sleep(10) print("カレーの準備が完了\n") def make_salad(): print("サラダの準備を開始\n") time.sleep(2) print("野菜を切る\n") time.sleep(2) print("サラダの準備が完了") def serve(): print("盛り付けを開始\n") time.sleep(1) print("盛り付けを終了。頂きます。\n") #スレッドを作成 rice = Thread(target=make_rice) curry = Thread(target=make_curry) salad = Thread(target=make_salad) serve = Thread(target=serve) #3つのスレッドを開始 rice.start() curry.start() salad.start() #3つのスレッドが全て終了するまで待機 rice.join() curry.join() salad.join() #最後に盛り付けのスレッドを実行 serve.start() serve.join() |
1つのプロセスで3つのスレッドを作り3つのタスクを並行処理し、最後に4つ目のスレッドを立てて4つ目のタスクを処理している。
コメント