Threading dalam aplikasi PyQt: Gunakan utas Qt atau utas Python?

116

Saya sedang menulis aplikasi GUI yang secara teratur mengambil data melalui koneksi web. Karena pengambilan ini membutuhkan waktu beberapa saat, ini menyebabkan UI menjadi tidak responsif selama proses pengambilan (tidak dapat dibagi menjadi beberapa bagian yang lebih kecil). Inilah mengapa saya ingin melakukan outsourcing koneksi web ke utas pekerja terpisah.

[Ya, saya tahu, sekarang saya punya dua masalah .]

Bagaimanapun, aplikasinya menggunakan PyQt4, jadi saya ingin tahu pilihan apa yang lebih baik: Gunakan utas Qt atau gunakan threadingmodul Python ? Apa kelebihan / kekurangan masing-masing? Atau apakah Anda memiliki saran yang sama sekali berbeda?

Edit (re bounty): Sementara solusi dalam kasus khusus saya mungkin akan menggunakan permintaan jaringan non-pemblokiran seperti yang disarankan Jeff Ober dan Lukáš Lalinský (jadi pada dasarnya menyerahkan masalah konkurensi ke implementasi jaringan), saya masih ingin yang lainnya jawaban mendalam untuk pertanyaan umum:

Apa keuntungan dan kerugian menggunakan thread PyQt4 (yaitu Qt) dibandingkan thread asli Python (dari threadingmodul)?


Edit 2: Terima kasih atas jawaban Anda. Meskipun tidak ada kesepakatan 100%, tampaknya ada konsensus luas bahwa jawabannya adalah "gunakan Qt", karena keuntungannya adalah integrasi dengan seluruh pustaka, sementara tidak menyebabkan kerugian nyata.

Bagi siapapun yang ingin memilih di antara dua implementasi threading, saya sangat menyarankan mereka membaca semua jawaban yang disediakan di sini, termasuk utas milis PyQt yang ditautkan oleh abbot .

Ada beberapa jawaban yang saya pertimbangkan untuk hadiah itu; pada akhirnya saya memilih kepala biara untuk referensi eksternal yang sangat relevan; itu, bagaimanapun, panggilan dekat.

Terima kasih lagi.

balpha
sumber

Jawaban:

106

Ini dibahas belum lama ini di milis PyQt. Mengutip komentar Giovanni Bajo tentang hal itu:

Hampir sama. Perbedaan utamanya adalah QThreads lebih terintegrasi dengan Qt (sinyal / slot asynchrnous, event loop, dll.). Selain itu, Anda tidak dapat menggunakan Qt dari utas Python (Anda tidak dapat, misalnya, memposting acara ke utas utama melalui QApplication.postEvent): Anda memerlukan QThread agar berfungsi.

Aturan umum mungkin menggunakan QThreads jika Anda akan berinteraksi dengan Qt, dan menggunakan benang Python sebaliknya.

Dan beberapa komentar sebelumnya tentang subjek ini dari penulis PyQt: "mereka berdua membungkus implementasi thread asli yang sama". Dan kedua implementasi menggunakan GIL dengan cara yang sama.

kepala biara
sumber
2
Jawaban yang bagus, tetapi saya pikir Anda harus menggunakan tombol blockquote untuk menunjukkan dengan jelas di mana Anda sebenarnya tidak meringkas tetapi mengutip Giovanni Bajo dari milis :)
c089
2
Saya bertanya-tanya mengapa Anda tidak dapat memposting acara ke utas utama melalui QApplication.postEvent () dan memerlukan QThread untuk itu? Saya rasa saya telah melihat orang-orang melakukannya dan itu berhasil.
Trilarion
1
Saya telah menelepon QCoreApplication.postEventdari utas Python dengan kecepatan 100 kali per detik, dalam aplikasi yang berjalan lintas platform dan telah diuji selama 1000 jam. Saya tidak pernah melihat ada masalah dari itu. Saya pikir tidak apa-apa selama objek tujuan terletak di MainThread atau QThread. Saya juga membungkusnya di perpustakaan yang bagus, lihat qtutils .
three_pineapples
2
Mengingat sifat yang sangat dipilih dari pertanyaan dan jawaban ini, saya pikir ada baiknya menunjukkan jawaban SO terbaru oleh ekhumoro yang menguraikan kondisi di mana aman untuk menggunakan metode Qt tertentu dari utas Python. Ini sesuai dengan perilaku yang diamati yang telah dilihat oleh saya dan @Trilarion.
three_pineapples
33

Utas Python akan lebih sederhana dan lebih aman, dan karena ini untuk aplikasi berbasis I / O, mereka dapat melewati GIL. Yang mengatakan, apakah Anda telah mempertimbangkan non-blocking I / O menggunakan Twisted atau non-blocking sockets / select?

EDIT: lebih lanjut tentang utas

Benang Python

Utas Python adalah utas sistem. Namun, Python menggunakan kunci interpreter global (GIL) untuk memastikan bahwa interpreter hanya mengeksekusi blok ukuran tertentu dari instruksi kode byte pada satu waktu. Untungnya, Python merilis GIL selama operasi input / output, membuat utas berguna untuk mensimulasikan I / O non-pemblokiran.

Peringatan penting: Ini bisa menyesatkan, karena jumlah instruksi kode byte tidak sesuai dengan jumlah baris dalam program. Bahkan satu tugas mungkin tidak atomic dengan Python, jadi kunci mutex diperlukan untuk setiap blok kode yang harus dieksekusi secara atomik, bahkan dengan GIL.

Benang QT

Saat Python menyerahkan kontrol ke modul yang dikompilasi pihak ketiga, GIL akan dirilis. Ini menjadi tanggung jawab modul untuk memastikan atomicity jika diperlukan. Saat kontrol dikembalikan, Python akan menggunakan GIL. Ini dapat membuat penggunaan pustaka pihak ketiga dalam hubungannya dengan utas membingungkan. Bahkan lebih sulit lagi untuk menggunakan library threading eksternal karena menambahkan ketidakpastian tentang di mana dan kapan kontrol berada di tangan modul vs interpreter.

Benang QT beroperasi dengan GIL dirilis. Benang QT dapat mengeksekusi kode pustaka QT (dan kode modul terkompilasi lainnya yang tidak memperoleh GIL) secara bersamaan. Namun, kode Python yang dieksekusi dalam konteks utas QT masih memperoleh GIL, dan sekarang Anda harus mengelola dua set logika untuk mengunci kode Anda.

Pada akhirnya, baik utas QT dan utas Python adalah pembungkus di sekitar utas sistem. Utas Python sedikit lebih aman untuk digunakan, karena bagian-bagian yang tidak ditulis dengan Python (secara implisit menggunakan GIL) menggunakan GIL dalam kasus apa pun (meskipun peringatan di atas masih berlaku.)

I / O non-pemblokiran

Untaian menambah kerumitan luar biasa pada aplikasi Anda. Terutama ketika berhadapan dengan interaksi yang sudah kompleks antara interpreter Python dan kode modul yang dikompilasi. Meskipun banyak yang menganggap pemrograman berbasis peristiwa sulit untuk diikuti, I / O non-pemblokiran berbasis peristiwa seringkali jauh lebih sulit untuk dipikirkan daripada utas.

Dengan asynchronous I / O, Anda selalu bisa yakin bahwa, untuk setiap deskriptor terbuka, jalur eksekusi konsisten dan teratur. Jelas ada, masalah yang harus diatasi, seperti apa yang harus dilakukan ketika kode bergantung pada satu saluran terbuka lebih jauh tergantung pada hasil kode yang akan dipanggil ketika saluran terbuka lainnya mengembalikan data.

Satu solusi bagus untuk I / O berbasis acara dan non-pemblokiran adalah pustaka Diesel baru . Itu terbatas pada Linux saat ini, tetapi sangat cepat dan cukup elegan.

Ini juga sepadan dengan waktu Anda untuk mempelajari pyevent , pembungkus di sekitar perpustakaan libevent yang luar biasa, yang menyediakan kerangka kerja dasar untuk pemrograman berbasis acara menggunakan metode tercepat yang tersedia untuk sistem Anda (ditentukan pada waktu kompilasi).

Jeff Ober
sumber
Re Twisted dll: Saya menggunakan perpustakaan pihak ketiga yang melakukan hal-hal jaringan yang sebenarnya; Saya ingin menghindari menambal di dalamnya. Tapi saya masih akan memeriksanya, terima kasih.
balpha
2
Tidak ada yang benar-benar dapat melewati GIL. Tapi Python merilis GIL selama operasi I / O. Python juga merilis GIL ketika 'menyerahkan' ke modul yang dikompilasi, yang bertanggung jawab untuk memperoleh / merilis GIL itu sendiri.
Jeff Ober
2
Pembaruannya salah. Kode Python bekerja dengan cara yang persis sama di utas Python daripada di QThread. Anda memperoleh GIL ketika Anda menjalankan kode Python (dan kemudian Python mengelola eksekusi antar utas), Anda melepaskannya ketika Anda menjalankan kode C ++. Tidak ada perbedaan sama sekali.
Lukáš Lalinský
1
Tidak, intinya adalah bagaimana pun Anda membuat utas, penerjemah Python tidak peduli. Semua yang dipedulikan adalah ia dapat memperoleh GIL dan setelah instruksi X ia dapat melepaskan / memperolehnya kembali. Misalnya, Anda dapat menggunakan ctypes untuk membuat panggilan balik dari pustaka C, yang akan dipanggil di utas terpisah, dan kode akan berfungsi dengan baik bahkan tanpa menyadarinya adalah utas yang berbeda. Tidak ada yang istimewa dari modul utas.
Lukáš Lalinský
1
Anda mengatakan bagaimana QThread berbeda tentang penguncian dan bagaimana "Anda harus mengelola dua set logika untuk mengunci kode Anda". Apa yang saya katakan adalah tidak ada bedanya sama sekali. Saya dapat menggunakan ctypes dan pthread_create untuk memulai utas, dan ini akan bekerja dengan cara yang persis sama. Kode Python tidak harus peduli dengan GIL.
Lukáš Lalinský
21

Keuntungannya QThreadadalah terintegrasi dengan pustaka Qt lainnya. Artinya, metode berbasis thread di Qt perlu mengetahui di thread mana mereka menjalankan, dan untuk memindahkan objek antar thread, Anda perlu menggunakannya QThread. Fitur berguna lainnya adalah menjalankan event loop Anda sendiri dalam sebuah thread.

Jika Anda mengakses server HTTP, Anda harus mempertimbangkan QNetworkAccessManager.

Lukáš Lalinský
sumber
1
Terlepas dari apa yang saya komentari atas jawaban Jeff Ober, QNetworkAccessManagertampak menjanjikan. Terima kasih.
balpha
13

Saya bertanya pada diri sendiri pertanyaan yang sama ketika saya bekerja di PyTalk .

Jika Anda menggunakan Qt, Anda harus menggunakannya QThreaduntuk dapat menggunakan kerangka kerja Qt dan terutama sistem sinyal / slot.

Dengan mesin sinyal / slot, Anda akan dapat berbicara dari utas ke utas lainnya dan dengan setiap bagian dari proyek Anda.

Selain itu, tidak ada pertanyaan performa tentang pilihan ini karena keduanya adalah pengikatan C ++.

Inilah pengalaman saya tentang PyQt dan utas.

Saya mendorong Anda untuk menggunakan QThread.

Natim
sumber
9

Jeff memiliki beberapa poin bagus. Hanya satu utas utama yang dapat melakukan pembaruan GUI. Jika Anda memang perlu memperbarui GUI dari dalam utas, sinyal koneksi antrian Qt-4 memudahkan pengiriman data melintasi utas dan secara otomatis akan dipanggil jika Anda menggunakan QThread; Saya tidak yakin apakah mereka akan melakukannya jika Anda menggunakan utas Python, meskipun mudah untuk menambahkan parameter ke connect().

Kaleb Pederson
sumber
5

Saya juga tidak bisa merekomendasikan, tetapi saya dapat mencoba menjelaskan perbedaan antara utas CPython dan Qt.

Pertama-tama, utas CPython tidak berjalan secara bersamaan, setidaknya bukan kode Python. Ya, mereka membuat utas sistem untuk setiap utas Python, namun hanya utas yang saat ini memegang Kunci Penerjemah Global yang diizinkan untuk berjalan (ekstensi C dan kode FFI mungkin melewatinya, tetapi bytecode Python tidak dijalankan sementara utas tidak memegang GIL).

Di sisi lain, kami memiliki utas Qt, yang pada dasarnya merupakan lapisan umum di atas utas sistem, tidak memiliki Kunci Penerjemah Global, dan dengan demikian mampu berjalan secara bersamaan. Saya tidak yakin bagaimana PyQt mengatasinya, namun kecuali utas Qt Anda memanggil kode Python, mereka harus dapat berjalan secara bersamaan (bar berbagai kunci tambahan yang mungkin diterapkan dalam berbagai struktur).

Untuk penyempurnaan ekstra, Anda dapat memodifikasi jumlah instruksi bytecode yang ditafsirkan sebelum mengalihkan kepemilikan GIL - nilai yang lebih rendah berarti lebih banyak pengalihan konteks (dan mungkin daya tanggap yang lebih tinggi) tetapi kinerja yang lebih rendah per utas individu (pengalih konteks memiliki biayanya - jika Anda coba ganti setiap beberapa instruksi itu tidak membantu kecepatan.)

Semoga membantu dengan masalah Anda :)

p_l
sumber
7
Penting untuk dicatat di sini: PyQt QThreads menggunakan Kunci Penerjemah Global . Semua kode Python mengunci GIL, dan QThread yang Anda jalankan di PyQt akan menjalankan kode Python. (Jika tidak, Anda sebenarnya tidak menggunakan bagian "Py" dari PyQt :). Jika Anda memilih untuk menunda dari kode Python itu ke perpustakaan C eksternal, GIL akan dirilis, tetapi itu benar terlepas dari apakah Anda menggunakan benang Python atau benang Qt.
Quark
Itu sebenarnya yang ingin saya sampaikan, bahwa semua kode Python mengambil kunci, tetapi tidak masalah untuk kode C / C ++ yang berjalan di utas terpisah
p_l
0

Saya tidak dapat mengomentari perbedaan persis antara utas Python dan PyQt, tetapi saya telah melakukan apa yang Anda coba gunakan QThread, QNetworkAcessManagerdan memastikan untuk memanggil QApplication.processEvents()saat utas masih hidup. Jika daya tanggap GUI benar-benar masalah yang Anda coba selesaikan, nanti akan membantu.

brianz
sumber
1
QNetworkAcessManagertidak membutuhkan utas atau processEvents. Ini menggunakan operasi IO asynchronous.
Lukáš Lalinský
Ups ... ya, saya menggunakan kombinasi QNetworkAcessManagerdan httplib2. Kode async saya menggunakan httplib2.
brianz