確かに、mata が述べたように、フリーズは、UI の更新も処理する同じ (メイン) スレッドですべての作業を行うことから発生します。応答性の問題を解決する 1 つの方法は、ブロッキング コードで QApplication.processEvents() を頻繁に使用することです。十分な頻度があれば、応答性の高い GUI の印象をユーザーに与えることができます。
ただし、Python でのスレッドの使用 (ネイティブまたは QThread) は、常に機能するとは限りません。これは、Global Interpreter Lock (GIL、ウィキには短いイントロがあります) が存在するためです。つまり、Python では複数のスレッドが同時にコードを実行することはできません。
バックグラウンド タスクが軽い場合、または IO に基づいている場合は、Python のほとんどの IO ヘビー モジュールがジョブを実行中に GIL をリリースするため、これを回避できます。ただし、Python で大量の計算を実行している場合、GIL はプロセスによってロックされるため、UI は依然として応答しません。
PySide を使用して構築された次の例を考えてみましょう。
import sys, random
from threading import Thread
from time import sleep
from urllib import urlopen
from PySide import QtCore, QtGui
class Window(QtGui.QMainWindow):
update_signal = QtCore.Signal(int)
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.progress_bar = QtGui.QProgressBar(self)
self.progress_bar.setRange(0, 10)
self.setCentralWidget(self.progress_bar)
self.update_signal[int].connect(self.progress_bar.setValue)
self.show()
self.t = Thread(target=self.worker)
self.t.start()
def worker(self):
while self.progress_bar.value() < 10:
self.update_signal.emit(self.progress_bar.value()+1)
print "Starting Sleep"
sleep(5)
print "End of Sleep"
if __name__ == '__main__':
qapp = QtGui.QApplication(sys.argv)
win = Window()
sys.exit(qapp.exec_())
次に、ワーカー関数を次のように置き換えてみてください。
def worker(self):
while self.progress_bar.value() < 10:
self.update_signal.emit(self.progress_bar.value()+1)
v = 0
print "Starting Add"
for i in xrange(5000000):
v = v+random.uniform(0, 100)
print "End of Add"
最初のケースでは、sleep() の呼び出しによって GIL が解放されるため、レスポンシブ UI が維持されます。しかし、計算量の多いアルゴリズムがロックを保持するため、2 番目の例はそうではありません。
解決策の 1 つは、マルチプロセッシングパッケージを使用することです。ドキュメントから:
multiprocessing は、threading モジュールと同様の API を使用してプロセスの生成をサポートするパッケージです。マルチプロセッシング パッケージは、ローカルとリモートの両方の同時実行性を提供し、スレッドの代わりにサブプロセスを使用してグローバル インタープリター ロックを効果的に回避します。このため、マルチプロセッシング モジュールを使用すると、プログラマは特定のマシンで複数のプロセッサを完全に活用できます。
Python のマルチポセッシングを使用した簡単な例です。
また、さらに興味がある場合は、マルチプロセッシング手法に関するこのブログ投稿が興味深いかもしれません。