Pythonのマルチスレッド
本稿では,マルチスレッドについて学んだことをまとめ,理解を深めるために記述する.
マルチスレッドについて
マルチスレッドとは、一つのコンピュータプログラムを実行する際に、複数の処理の流れを並行して進めること。また、そのような複数の処理の流れ。
プログラムをスレッドに分割すると,メモリコンテキストを共有しながら並行に実行できる.外部リソースを利用していない場合,シングルコアCPU上では,マルチスレッド化しても高速にならない.マルチコアCPU上でマルチスレッド化すると,各スレッドが別々のCPUに割り当てられ同時に並列して実行することでプログラムの速度が向上する.
スレッドとプロセスとの比較
簡易な定義,メモリ空間,コンテキストスイッチからの観点で特徴をまとめる.
定義
- プロセスは実行されたプログラムの実体
- スレッドはプロセスをさらに細かく分割した実行単位
メモリ空間
- プロセスは固有のメモリ空間を保持し,占有している.
- スレッドはメモリ空間を共有する
コンテキストスイッチ
- プロセスはコンテキストスイッチのコストがスレッドに比べると大きい.
コンテキストスイッチについて
コンテキストスイッチとは、コンピュータの処理装置(CPU)が現在実行している処理の流れ(プロセス、スレッド)を一時停止し、別のものに切り替えて実行を再開すること。
プロセスのコンテキストスイッチはメモリアドレス空間を切り替える必要があり,この操作は比較的にコストが高い操作.
以下,参考になった資料
https://code-examples.net/ja/q/530280
https://www.slideshare.net/ssuserc2d4c1/ss-124497965
これにより,それぞれに対して効率性と信頼性の観点から次の特徴が存在する.
効率性
マルチプロセスによる並列処理に比べて,マルチスレッドの方が一般的にメモリ空間を共有している分,効率性が高い.
信頼性
マルチスレッドはメモリ空間を共有しているため,あるデータが並列処理から使用される場合,データをアクセスされている処理から保護する必要がある.複数のスレッドが保護されていない1つのデータを同時に更新しようとすると,競合状態に陥り,予期しないエラーが発生.データを保護するためにロックをかける必要がある.データのロックには適切に利用するのは難しい.
一方,マルチプロセスはメモリ空間を共有することがないため,マルチスレッドで起こりうるデータの破損やデッドロックが発生する可能性が減少される.
グローバルインタプリタロック(GIL)
RubyやPythonに存在するグローバルインタプリタロック(以下,「GIL」と略記)が採用されている.
Pythonでは,Pythonのオブジェクトにアクセスするスレッドは常に1スレッドだけに制限される.これはなぜか.
まず,PythonをC言語で書かれた実装(CPython)はスレッドセーフではない.スレッドセーフではないという状況は,複数のスレッドが同時に実行したり同じデータを扱ったりすると,データが壊れてしまう状況を指す.ここで言及しているデータとは,例えば「共有されているメモリ領域の内容」が挙げられる.
スレッドセーフではないために生じるデータの破損を回避するための手段として,他のスレッドと共有してしまうことを防ぐ手段が存在する.
他スレッドとの共有を防ぐためには排他ロックの仕組みを採用する必要がある.この排他ロックをGILという.
故にGILによって,常にスレッドは1つに限定される.
以下の資料は大変に参考になった
http://blog.bonprosoft.com/1632
https://methane.hatenablog.jp/entry/20111203/1322900647
GILついてのPython公式ドキュメントの言及
マルチ CPU マシン上で Python を使いこなすには以下の二つの手段が挙げられている.
- タスクを複数の スレッド ではなく複数の プロセス に分けることを考える
- CPythonの拡張
GILの制限を加味した上でのマルチスレッドの利用
応答のよいインターフェスを作りたい場面
GUI操作によりファイルをあるディレクトリから別ディレクトリにコピーするシステムを考える.
要件としてマルチスレッドを使用して,コピー処理をバックグラウンドで実行し,GUIウィンドウはメインスレッドにより常に更新する.
これにより,ユーザには実行あるいは操作の進捗状況がリアルタイムにフィードバックされ作業の中断も行える.
ここでの応答性のよりインターフェスを作るというのは,時間のかかるタスクをバックグラウンドで処理したり,ユーザに一定時間内にフィードバックを返すようすること.これの実現方法としてマルチスレッドの利用がある.(パフォーマンス向上の目的ではなく,データ処理に時間がかかる場合においてもインターフェスをユーザが操作できるようにするため)
プロセスが外部リソースに依存している場面
プロセスが外部のリソースに依存している場合にはマルチスレッドにより高速化できる可能性がある.
外部サービスへ多数のHTTPリクエストを送信する場合,マルチスレッドがよく利用されているとのこと.
レスポンスを受け取るまでに時間のかかるWeb APIから複数の結果を取得したい場合,同期的に実行すると時間がかかる.
WebAPIと通信する場合には,並行するリクエスト(複数のリクエストが完全あるいは部分的に順序関係なく実行されても問題ない場合のリクエスト)が互いに応答時間にほぼ影響を与えずに並行処理されることがある.この並行処理の実現手段として,複数のリクエストを別々にスレッドとして実行する場合がある.
HTTPリクエストを実行する際,TCPソケットからの読み込み(recv()
)に時間がかかることが多い.CPythonではC言語のrecv()
関数の実行はGILを解放する.(これはブロッキングなI/O処理のためらしいがまだ理解がたりない.)
GIL解放によりマルチスレッドの利用が可能.
所感
PythonではI/O処理待ちにはスレッドは有用なのかなと.CPythonは自分にとってまだまだ難しい.
参考文献
http://ossforum.jp/node/579
https://ja.wikipedia.org/wiki/グローバルインタプリタロック
http://blog.bonprosoft.com/1632
https://methane.hatenablog.jp/entry/20111203/1322900647
http://e-words.jp/w/%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81.html
http://e-words.jp/w/%E3%83%9E%E3%83%AB%E3%83%81%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89.html
マスタリングTCP/IP 入門編 第5版
エキスパートPythonプログラミング改訂2版