絶対ダメ! マルチスレッドでの共通変数のインクリメント演算
Photo by Sebastiaan ter Burg
デバッグ中の発見
とあるマルチスレッドプログラムをPythonで書いていた際に遭遇した誤使用です。C/C++等の言語を使った場合には起こらない仕様なので、知っておくと役立つ時がくるはずです。これはPythonだけでなく、Rubyでも同様のことが起こるので、Rubyistさんも是非気をつけていただければと思います。
@ahaha_traderさんのご指摘により、C/C++でも同様のことが発生することを教えていただきました・・・。不勉強をお詫び申し上げます。
このブログを見ているような方々だとバグが生じる実際のコードと、結果をまずお見せしたほうが良いと思うので、サンプル用に作ったコードがこちらです。
バグが生じるソースコード
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 |
import Queue import threading class MultiThreadIncrement(object): def __init__(self, thread_amount): self.thread_amount = thread_amount self.thread_array = [None] * thread_amount self.processing_count = 0 def make_threads(self): for i in xrange(self.thread_amount): start_queue = Queue.Queue() result_queue = Queue.Queue() close_event = threading.Event() self.thread_array[i] = threading.Thread(target=self.thread_target, args=(start_queue, result_queue, close_event)) self.thread_array[i].start() self.thread_array[i].start_queue = start_queue self.thread_array[i].result_queue = result_queue self.thread_array[i].close_event = close_event def increment(self): for i in xrange(self.thread_amount): self.thread_array[i].start_queue.put(None) for i in xrange(self.thread_amount): self.thread_array[i].result_queue.get() def close(self): for i in xrange(self.thread_amount): self.thread_array[i].close_event.set() self.thread_array[i].start_queue.put(None) def thread_target(self, start_queue, result_queue, close_event): while True: start_queue.get() if close_event.is_set(): return self.processing_count += 1 result_queue.put(True) if __name__ == "__main__": mult = MultiThreadIncrement(12) mult.make_threads() for _ in xrange(1000): mult.increment() print mult.processing_count mult.close() |
やっている内容は、12個のスレッドを使って self.increment_count + 1 を12,000回繰り返すという単純処理です。
実行結果
1 |
increment result : 11971 |
本来、12*1,000の12,000を表示させたかったはずのこのコードなのですが、残念ながら29の違いが生じています。なぜこれが生じてしまうのでしょうか。
動作不良の理由
C/C++など、正式なインクリメントが適応されているような言語ですと、マルチスレッドで行っても、確実にプロセッサレベルで正確にインクリメント処理が行われるので問題がありません。
ですが、PythonやRubyのインクリメント処理は、C/C++で使用されるようなインクリメント処理とはまったく異なります。PythonやRubyの場合のインクリメントの記述自体は、インクリメントっぽく書かれていますが、実際に行われているのは以下の処理です。
1 2 |
# increment_count += 1 increment_count = increment_count + 1 |
ということは、increment_count + 1 の演算が行われた後の結果のPython/Rubyオブジェクトにincrement_countの参照が適応されます。簡単に言うと、演算と結果の格納が2つの処理になってしまっているのです。
この場合、マルチスレッドにて、increment_count + 1 の演算がポインタの割り当てより前に他のスレッドにて同時に行われてしまうというケースが生じます。それが、今回の検証の場合 29/12,000 の割合で生じてしまっていたのですね。
参考URL
誤使用の回避方法
回避する方法は、Queue.Queueクラスを使い、putされた個数を取得するという方法が簡単で便利です(12,000回もカウントするのには実用的ではないけれど)。他にもthreading.Lockを使う方法もありますが、マルチスレッドの利点が減ってしまうかもですね。
なかなかデバッグしにくい内容で手こずりましたが、バグの原因が特定できると納得の行く結果となりました。みなさんもお気をつけ下さいね!



About Fx-Kirin
2009年10月にFXを開始、翌年2010年5月から脱サラをしてFX業界に専念。 2012年10月頃から本格的に勝ち始め、一月で資産を倍にする、2年半月間負けなし等、安定した収支で2013年11月に生涯FX収支が1億を超える。 投資スタイルはシステムトレード。プログラミングの知識がほぼない状態から、独学で自分がしたいと思うことであればほぼ実現することが可能なレベルまで成長。好きな言語はRuby, Python。必要となればC++からVBA、Pascal等なんでも行う。MT4/MT5のプログラミングも得意。 2011年にはFXで稼いだ資金をもとにシンガポールに移住し、留学も兼ねて起業をチャレンジするほど、ビジネスを興すことに熱意がある。国内の業者を主に使い始めたことから、2012年に帰国。零細株式会社経営中。
- Web |
- More Posts (410)
Adsense
関連記事
-
-
Pythonで簡単自動化!PyAutoGuiが便利すぎて感動したのでご紹介
以前こんな記事を書いていたのですが、これがまったく不要になるとても便利なPython Libraryがあった
-
-
Python の超お手軽のネットワーク分散コンピューティングライブラリSCOOP
SCOOPとは ssh とPython の設定を適切にするだけで、簡単にネットワーク間での分散処理が実行できる。
-
-
Python Pandas からお手軽に highcharts が使える kanichart 作りました。
Kanichart fx-kirin/kanichart: Easy(簡易) plotting library.
-
-
Python x64 & MinGW64 環境の構築
流石に詰まりまくったのでまとめることにする。 MSYS2 をインストール 個人的にこれからメインで使いたいと思っ
-
-
PYPIへの登録を10秒でできるようになる方法
pip 使ってますよね Pythonを使っている人であれば、pip installでライブラリをインストールするこ
-
-
Flast-Sockets + redis-py で簡単 Websocket サーバー実装
参考にしたサイト Using WebSockets on Heroku with Python | Heroku
-
-
SQLAlchemy のマイグレーションライブラリ Alembic を使ってみる。
SQLAlchemyの作者が作ったデータベースマイグレーションツール。個人的には、SQLAlchemyは使わないが、
-
-
JupyterでボタンからJavascriptを実行して追加のアウトプットをさせない方法
Javascriptを実行するとアウトプットセルの行が増える これがとても面倒だった。上の画像のように、Wid
-
-
Rust で Python の拡張ライブラリ作成 と numpy との性能比較
この記事は Python Advent Calendar 5日目の記事です。遅れてすみません。 Rust で Py
-
-
Python 2, Python 3 で string を bytes に変換する。
共通の処理系にしておきたかったので、メモ書き。 [bm url="https://python-future.or
Adsense
NEW ENTRY
-
- Linux Mint 20 での日本語の設定について。
フォントの設定とかはいろいろなところで触れられているので、他にない情報だけ。 TL;DR F
-
- joblib によって謎のバグが起こる
joblib 0.14.1 にて確認.0.13.2 だと起こらない.import joblib を消
-
- Fixing kernel error AMD-Vi: Event logged IO_PAGE_FAULT on Ryzen Machine
My pc was periodically shut down on 7:40 am JS
-
- Ubuntu で仮想ディスプレイを使う
雑多な備忘録ですが、せっかくなので残しておきます。 Ubuntu 18.04 の resolv.c
-
- PYPIへの登録を10秒でできるようになる方法
pip 使ってますよね Pythonを使っている人であれば、pip installでライブラリ
Twitter
RSS
カテゴリー
-
人気記事一覧