15

PySide GUI アプリケーションでかなり一般的なことをしようとしています。CPU を集中的に使用するタスクをバックグラウンド スレッドに委任して、GUI の応答性を維持し、計算の進行中に進行状況インジケーターを表示することもできます。

これが私がやっていることです(私はPython 2.7、Linux x86_64でPySide 1.1.1を使用しています):

import sys
import time
from PySide.QtGui import QMainWindow, QPushButton, QApplication, QWidget
from PySide.QtCore import QThread, QObject, Signal, Slot

class Worker(QObject):
    done_signal = Signal()

    def __init__(self, parent = None):
        QObject.__init__(self, parent)

    @Slot()
    def do_stuff(self):
        print "[thread %x] computation started" % self.thread().currentThreadId()
        for i in range(30):
            # time.sleep(0.2)
            x = 1000000
            y = 100**x
        print "[thread %x] computation ended" % self.thread().currentThreadId()
        self.done_signal.emit()


class Example(QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

        self.work_thread = QThread()
        self.worker = Worker()
        self.worker.moveToThread(self.work_thread)
        self.work_thread.started.connect(self.worker.do_stuff)
        self.worker.done_signal.connect(self.work_done)

    def initUI(self):

        self.btn = QPushButton('Do stuff', self)
        self.btn.resize(self.btn.sizeHint())
        self.btn.move(50, 50)       
        self.btn.clicked.connect(self.execute_thread)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Test')    
        self.show()


    def execute_thread(self):
        self.btn.setEnabled(False)
        self.btn.setText('Waiting...')
        self.work_thread.start()
        print "[main %x] started" % (self.thread().currentThreadId())

    def work_done(self):
        self.btn.setText('Do stuff')
        self.btn.setEnabled(True)
        self.work_thread.exit()
        print "[main %x] ended" % (self.thread().currentThreadId())


def main():

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

アプリケーションは、ボタン付きの 1 つのウィンドウを表示します。ボタンが押されると、計算の実行中にボタンが無効になると思います。次に、ボタンを再度有効にする必要があります。

代わりに、ボタンを押すと、計算中にウィンドウ全体がフリーズし、計算が終了すると、アプリケーションの制御を取り戻すことができます。ボタンが無効になっているようには見えません。私が気づいた面白いことは、CPU 集中型の計算をdo_stuff()単純な time.sleep() に置き換えると、プログラムが期待どおりに動作することです。

何が起こっているのか正確にはわかりませんが、2 番目のスレッドの優先度が高すぎて、実際には GUI スレッドがスケジュールされないようになっているようです。2 番目のスレッドが BLOCKED 状態になると (a で発生するようにsleep())、GUI が実際に実行され、期待どおりにインターフェイスが更新される可能性があります。ワーカースレッドの優先度を変更しようとしましたが、Linux ではできないようです。

また、スレッド ID を出力しようとしましたが、正しく実行されているかどうかわかりません。そうであれば、スレッド アフィニティは正しいようです。

私も PyQt でプログラムを試してみましたが、動作はまったく同じなので、タグとタイトルです。PySide の代わりに PyQt4 で実行できる場合は、アプリケーション全体を PyQt4 に切り替えることができます

4

2 に答える 2

14

これはおそらく、Python の GIL を保持するワーカー スレッドが原因です。一部の Python 実装では、一度に実行できる Python スレッドは 1 つだけです。GIL は、他のスレッドが Python コードを実行するのを防ぎ、GIL を必要としない関数呼び出し中に解放されます。

たとえば、IO は Python インタープリターではなくオペレーティング システムによって処理されるため、GIL は実際の IO 中に解放されます。

ソリューション:

  1. time.sleep(0)どうやら、ワーカースレッドで使用して他のスレッドに譲ることができます(このSOの質問によると)。定期的にtime.sleep(0)自分自身を呼び出す必要があり、GUI スレッドはバックグラウンド スレッドがこの関数を呼び出している間だけ実行されます。

  2. ワーカー スレッドが十分に自己完結型である場合は、それを完全に別のプロセスに配置し、ピクルされたオブジェクトをパイプ経由で送信して通信できます。フォアグラウンド プロセスで、バックグラウンド プロセスとの IO を行うワーカー スレッドを作成します。ワーカー スレッドは CPU 操作ではなく IO を実行するため、GIL は保持されず、完全に応答性の高い GUI スレッドが得られます。

  3. 一部の Python 実装 (JPython および IronPython) には GIL がありません。

CPython のスレッドは、 CPU を集中的に使用するタスクをバックグラウンドに配置するのではなく、IO 操作を多重化する場合にのみ実際に役立ちます。多くのアプリケーションでは、CPython 実装でのスレッド化は根本的に壊れており、当面はそのままの状態が続く可能性があります。

于 2012-06-29T17:03:12.077 に答える