プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python
プロセスとスレッド Cpuの使い方の違い in python

Editor's Notes

  • #2 P1:
  • #3 P2:Pythonを好きになって続けてきて、ようやく並列処理をしたいシチュエーションが出てきたのでそういったモジュールが用意されていないか探してみたところ、「theading」というモジュールがあったので使ってみることにしました。
  • #4 P3:しかし、実際使ってみると問題がありましたので、今日はその解決策と原因について話していきます。
  • #5 P4:まず、並列処理させたのに処理時間が短くなりませんでした。
  • #6 P5:並列処理に対する期待は、「遊んでいるCPUをなくして、できるだけCPUをぶん回して計算したい」、「結果、直列でやったら長時間かかる処理を並列させて短時間で終わらせたい」というものがあると思います。
  • #7 P6:そのため、スレッドモジュールを使ってCPUを使うような処理を並列に実施させてみました。期待通り遊んでいるCPUを使ってくれればスレッド数を増やしても処理時間はあまり変わらないはずです。
  • #8 P7:結果は、期待通りには動いてくれませんでした。処理時間は計算量に正比例し、 CPUの使用率は1コア分で高止まりしてしまいました。要するに直列に計算させた時と変わっていません。
  • #9 P8:そこで、スレッドではなくプロセスを使った目的達成しました。
  • #10 P9:Pythonには並列処理させるモジュールに「threading」の他に「 multiprocessing」というものがあります。ここの説明を読むと「マルチプロセッサマシンの性能を最大限に活用することができる」とあったので早速使ってみました。
  • #11 P10:結果、今度は期待通り動いてくれました。手持ちのPCは4コアありますので、4プロセス立ち上げたところまでCPUの使用率はほぼ比例して上がっていっています。3プロセス目、4プロセス目になると若干上がり方が減少していますが、これは、OSのプロセス等他のものも動作しているためと予想します。それに応じて処理時間も3プロセス目からは若干長くなりだしています。とはいえ、直列処理よりは処理時間が短くなっているため、並列処理としては成功と言えると判断します。
  • #12 P11:でも、なぜプロセスだとCPUを使ってくれて、スレッドでは使ってくれないのでしょうか。
  • #13 P12:調べてみるとPythonというインタプリタとしての仕様に原因があり、その根本はC言語にあることがわかりました。
  • #14 P13:まず、原因を探るべく、「multiprocessing」の紹介を読み直すと「グローバルインタプリタロックの問題を避ける工夫が行われている」といった気になる文章がありました。キーワードと簡易的な説明文を読んでもよく分からないので「グローバルインタプリタロック」についてもう少し調べてみることにしました。
  • #15 P14:前提として基本のPythonはC言語によって書かれています。Pythonはインタプリタ言語ですので、インタプリタがC言語で書かれていて、pyファイルに書いたPythonソースコードをインタプリタが字句解析や構文解析をして機械語に動的に翻訳してくれています。
  • #16 P15:そのインタプリタはもちろんC言語の様々なライブラリを使っているのですが、ライブラリの中にはスレッドセーフでないものもあります。標準ライブラリもスレッドセーフでないものがあるそうです。Pythonインタプリタではそういったライブラリも使用しています。
  • #17 P16:さて、スレッドセーフとはどういった状態でしょうか。ざっくり言ってしまえば、並列処理しても問題が発生しないことを指します。問題はいくつかありますが、特にデータの整合性が崩れないことを指すことが多いです。スレッドセーフでない、データの整合性がとれないことについてこの図で説明します。関数Aがグローバル変数Aを取得して処理した結果を再度グローバル変数に戻すとします。関数Bも同様な処理をしたとします。関数Aも関数Bも自身が処理している間にグローバル変数Aの値が書き換わってほしくない場合、本来であれば関数Aの後に関数Bが順に処理されて欲しいはずです。しかし、同時に処理が走ってしまった場合、一方が処理をしている間にグローバル変数Aの内容が変わってしまう可能性があります。このような問題が発生する場合をスレッドセーフでないと言います。
  • #18 P17:そうすると、インタプリタとして問題が発生しますので、上位レイヤのPythonレベルでも動作する上でなんらかの問題が発生する可能性がでてきますね。
  • #19 P18:そこで、GILという機構をインタプリタに実装して、インタプリタで処理をする場合、同時に動作できるのは一つの処理ということにしました。で、処理を細切れにして順繰り処理することであたかも並列処理をさせているように見せかけることにしました。これが「threading」モジュールによる実装になります。
  • #20 P19:では、なぜ「multiprocessing」モジュールを使うとそれが回避できるのでしょうか。これはスレッドとプロセスの特定の違いによります。
  • #21 P20:スレッドは一つのプロセス上で動きます。OSはプロセス単位でメモリ空間を与えるので、プロセス内にあるスレッドはメモリ空間を共有します。メモリ空間を共有するとはグローバル変数がそのメモリ空間に同居している処理同士で共有して使われることを指します。そのため、スレッドセーフに実装されていなければ、GILがそこをカバーする必要がでてきます。
  • #22 P21:しかし、プロセスに処理を切り出すことによってメモリ空間は共有されなくなるので、各プロセス内でスレッド処理を実装しない限りGILの影響を受けなくなります。従って、並列処理をさせても処理が細切れにならなくなり、CPUもそれぞれ利用することができるようになります。
  • #23 P22:まとめとして、インタプリタ型の言語を使う場合は、スレッドで目的の並列処理が可能か確認が必要ということがわかりました。また、目的によってはマルチプロセス化を考える必要があることもわかりました。
  • #24 P23:このプレゼンのために作成したソースコードは以下にアップしていますので、試してみてください。ツッコミ歓迎です。 https://github.com/Euphoricwavism/cpu_test_for_process_and_thread