Luồng trong ứng dụng PyQt: Sử dụng chủ đề Qt hoặc chủ đề Python?


116

Tôi đang viết một ứng dụng GUI thường xuyên truy xuất dữ liệu thông qua kết nối web. Vì quá trình truy xuất này mất một thời gian, điều này khiến UI không phản hồi trong quá trình truy xuất (nó không thể được chia thành các phần nhỏ hơn). Đây là lý do tại sao tôi muốn thuê ngoài kết nối web đến một luồng công nhân riêng biệt.

[Vâng, tôi biết, bây giờ tôi có hai vấn đề .]

Dù sao, ứng dụng sử dụng PyQt4, vì vậy tôi muốn biết lựa chọn nào tốt hơn: Sử dụng các luồng của Qt hoặc sử dụng threadingmô-đun Python ? Ưu điểm / nhược điểm của mỗi loại là gì? Hay bạn có một đề nghị hoàn toàn khác nhau?

Chỉnh sửa (re bounty): Mặc dù giải pháp trong trường hợp cụ thể của tôi có thể sẽ sử dụng một yêu cầu mạng không chặn như Jeff OberLukáš Lalinský đề xuất (vì vậy về cơ bản để lại các vấn đề tương tranh cho việc triển khai mạng), tôi vẫn muốn nhiều hơn câu trả lời sâu sắc cho câu hỏi chung:

Những lợi thế và bất lợi của việc sử dụng các luồng của PyQt4 (tức là Qt) so với các luồng Python gốc (từ threadingmô-đun) là gì?


Chỉnh sửa 2: Cảm ơn tất cả các bạn câu trả lời. Mặc dù không có thỏa thuận 100%, nhưng dường như có sự đồng thuận rộng rãi rằng câu trả lời là "sử dụng Qt", vì lợi thế của việc đó là tích hợp với phần còn lại của thư viện, trong khi không gây ra bất lợi thực sự.

Đối với bất kỳ ai muốn lựa chọn giữa hai triển khai luồng, tôi khuyên họ nên đọc tất cả các câu trả lời được cung cấp ở đây, bao gồm cả chuỗi danh sách gửi thư PyQt mà abbot liên kết đến.

Có một số câu trả lời tôi đã xem xét cho tiền thưởng; cuối cùng tôi đã chọn trụ trì cho tài liệu tham khảo bên ngoài rất phù hợp; đó là, tuy nhiên, một cuộc gọi gần

Cảm ơn một lần nữa.

Câu trả lời:


106

Điều này đã được thảo luận cách đây không lâu trong danh sách gửi thư của PyQt. Trích dẫn ý kiến của Giovanni Bajo về chủ đề này:

Nó hầu như giống nhau. Sự khác biệt chính là QThread được tích hợp tốt hơn với Qt (tín hiệu / khe không đồng bộ, vòng lặp sự kiện, v.v.). Ngoài ra, bạn không thể sử dụng Qt từ một luồng Python (ví dụ bạn không thể đăng sự kiện lên luồng chính thông qua QApplication.postEvent): bạn cần một QThread để nó hoạt động.

Một nguyên tắc chung có thể là sử dụng QThread nếu bạn sẽ tương tác bằng cách nào đó với Qt và sử dụng các luồng Python khác.

Và một số nhận xét trước đó về chủ đề này từ tác giả của PyQt: "cả hai đều là các hàm bao quanh cùng một triển khai luồng gốc". Và cả hai triển khai đều sử dụng GIL theo cùng một cách.


2
Câu trả lời hay, nhưng tôi nghĩ bạn nên sử dụng nút blockquote để hiển thị rõ ràng bạn đang ở đâu trong thực tế không tóm tắt nhưng trích dẫn Giovanni Bajo từ danh sách gửi thư :)
c089

2
Tôi tự hỏi tại sao bạn không thể đăng các sự kiện lên chủ đề chính thông qua QApplication.postEvent () và cần một QThread cho điều đó? Tôi nghĩ rằng tôi đã thấy mọi người làm điều đó và nó đã làm việc.
Trilarion

1
Tôi đã gọi QCoreApplication.postEventtừ một luồng Python với tốc độ 100 lần mỗi giây, trong một ứng dụng chạy đa nền tảng và đã được thử nghiệm trong 1000 giờ. Tôi chưa bao giờ thấy bất kỳ vấn đề từ đó. Tôi nghĩ rằng nó ổn miễn là đối tượng đích được đặt trong MainThread hoặc QThread. Tôi cũng gói nó trong một thư viện đẹp, xem qtutils .
ba_pineapples

2
Do tính chất được đánh giá cao của câu hỏi và câu trả lời này, tôi nghĩ rằng đáng để chỉ ra một câu trả lời SO gần đây của ekhumoro , trong đó giải thích các điều kiện an toàn khi sử dụng các phương thức Qt nhất định từ các luồng Python. Người bạn thân này với hành vi được quan sát đã được nhìn thấy bởi chính tôi và @Trilarion.
ba_pineapples

33

Các luồng của Python sẽ đơn giản và an toàn hơn, và vì nó dành cho ứng dụng dựa trên I / O, nên chúng có thể bỏ qua GIL. Điều đó nói rằng, bạn đã xem xét I / O không chặn bằng cách sử dụng ổ cắm Twisted hoặc không chặn / select chưa?

EDIT: thêm về chủ đề

Chủ đề Python

Chủ đề của Python là chủ đề hệ thống. Tuy nhiên, Python sử dụng khóa trình thông dịch toàn cầu (GIL) để đảm bảo rằng trình thông dịch chỉ thực hiện một khối kích thước nhất định của các lệnh mã byte tại một thời điểm. May mắn thay, Python phát hành GIL trong các hoạt động đầu vào / đầu ra, làm cho các luồng trở nên hữu ích để mô phỏng I / O không chặn.

Thông báo trước quan trọng: Điều này có thể gây hiểu nhầm, vì số lượng các lệnh mã byte không tương ứng với số lượng dòng trong một chương trình. Ngay cả một nhiệm vụ duy nhất có thể không phải là nguyên tử trong Python, do đó, khóa mutex là cần thiết cho bất kỳ khối mã nào phải được thực thi nguyên tử, ngay cả với GIL.

Chủ đề QT

Khi Python trao quyền kiểm soát cho mô-đun được biên dịch của bên thứ 3, nó sẽ giải phóng GIL. Nó trở thành trách nhiệm của mô-đun để đảm bảo tính nguyên tử khi cần thiết. Khi điều khiển được chuyển trở lại, Python sẽ sử dụng GIL. Điều này có thể làm cho việc sử dụng các thư viện bên thứ 3 kết hợp với các chủ đề gây nhầm lẫn. Việc sử dụng một thư viện luồng bên ngoài thậm chí còn khó khăn hơn bởi vì nó làm tăng tính không chắc chắn về vị trí và thời điểm kiểm soát nằm trong tay của mô-đun so với trình thông dịch.

Chủ đề QT hoạt động với GIL được phát hành. Các luồng QT có thể thực thi mã thư viện QT (và mã mô-đun được biên dịch khác không thu được GIL) đồng thời. Tuy nhiên, mã Python được thực thi trong ngữ cảnh của luồng QT vẫn thu được GIL và bây giờ bạn phải quản lý hai bộ logic để khóa mã của mình.

Cuối cùng, cả luồng QT và luồng Python đều là các hàm bao quanh các luồng hệ thống. Các luồng Python an toàn hơn một chút để sử dụng, vì các phần không được viết bằng Python (hoàn toàn sử dụng GIL) sử dụng GIL trong mọi trường hợp (mặc dù cảnh báo ở trên vẫn được áp dụng.)

Không chặn I / O

Chủ đề thêm phức tạp đặc biệt cho ứng dụng của bạn. Đặc biệt là khi xử lý sự tương tác vốn đã phức tạp giữa trình thông dịch Python và mã mô-đun được biên dịch. Mặc dù nhiều người thấy lập trình dựa trên sự kiện khó theo dõi, nhưng I / O không chặn dựa trên sự kiện thường khó lý luận hơn nhiều so với các luồng.

Với I / O không đồng bộ, bạn luôn có thể chắc chắn rằng, đối với mỗi mô tả mở, đường dẫn thực hiện là nhất quán và có trật tự. Rõ ràng, có những vấn đề phải được giải quyết, chẳng hạn như phải làm gì khi mã phụ thuộc vào một kênh mở hơn nữa phụ thuộc vào kết quả của mã được gọi khi một kênh mở khác trả về dữ liệu.

Một giải pháp hay cho I / O không chặn dựa trên sự kiện là thư viện Diesel mới . Nó bị hạn chế đối với Linux tại thời điểm này, nhưng nó cực kỳ nhanh và khá thanh lịch.

Cũng đáng để bạn dành thời gian để học pyevent , một trình bao bọc xung quanh thư viện libevent tuyệt vời, cung cấp một khung cơ bản cho lập trình dựa trên sự kiện bằng phương pháp nhanh nhất có sẵn cho hệ thống của bạn (xác định tại thời gian biên dịch).


Re Twisted, v.v .: Tôi sử dụng một thư viện bên thứ ba thực hiện các công cụ kết nối mạng thực tế; Tôi muốn tránh vá xung quanh nó. Nhưng tôi vẫn sẽ xem xét điều đó, cảm ơn.
balpha

2
Không có gì thực sự bỏ qua GIL. Nhưng Python phát hành GIL trong các hoạt động I / O. Python cũng phát hành GIL khi 'bàn giao' cho các mô-đun được biên dịch, chịu trách nhiệm thu nhận / phát hành GIL.
Jeff Ober

2
Bản cập nhật chỉ là sai. Mã Python chạy chính xác theo cùng một cách trong một luồng Python so với trong QThread. Bạn sử dụng GIL khi bạn chạy mã Python (và sau đó Python quản lý việc thực thi giữa các luồng), bạn giải phóng nó khi bạn chạy mã C ++. Không có sự khác biệt nào cả.
Lukáš Lalinský

1
Không, vấn đề là dù bạn tạo chủ đề như thế nào, trình thông dịch Python không quan tâm. Tất cả những gì nó quan tâm là nó có thể có được GIL và sau các hướng dẫn X, nó có thể giải phóng / yêu cầu nó. Ví dụ, bạn có thể sử dụng ctypes để tạo một cuộc gọi lại từ thư viện C, sẽ được gọi trong một luồng riêng biệt và mã sẽ hoạt động tốt mà không cần biết đó là một luồng khác. Thực sự không có gì đặc biệt về mô-đun chủ đề.
Lukáš Lalinský

1
Bạn đang nói QThread khác nhau như thế nào về việc khóa và cách "bạn phải quản lý hai bộ logic để khóa mã của mình". Điều tôi đang nói là nó không khác biệt chút nào. Tôi có thể sử dụng ctypes và pthread_create để bắt đầu chuỗi, và nó sẽ hoạt động chính xác theo cùng một cách. Mã Python đơn giản là không cần phải quan tâm đến GIL.
Lukáš Lalinský

21

Ưu điểm của QThreadnó là nó được tích hợp với phần còn lại của thư viện Qt. Đó là, các phương thức nhận biết luồng trong Qt sẽ cần biết chúng chạy trong luồng nào và để di chuyển các đối tượng giữa các luồng, bạn sẽ cần sử dụng QThread. Một tính năng hữu ích khác là chạy vòng lặp sự kiện của riêng bạn trong một chuỗi.

Nếu bạn đang truy cập máy chủ HTTP, bạn nên xem xét QNetworkAccessManager.


1
Ngoài những gì tôi nhận xét về câu trả lời của Jeff Ober, có QNetworkAccessManagervẻ đầy hứa hẹn. Cảm ơn.
balpha

13

Tôi đã tự hỏi mình câu hỏi tương tự khi tôi làm việc với PyTalk .

Nếu bạn đang sử dụng Qt, bạn cần sử dụng QThreadđể có thể sử dụng khung Qt và đặc biệt là hệ thống tín hiệu / khe cắm.

Với công cụ tín hiệu / khe cắm, bạn sẽ có thể nói chuyện từ luồng này sang luồng khác và với mọi phần trong dự án của bạn.

Hơn nữa, không có câu hỏi về hiệu suất nào về lựa chọn này vì cả hai đều là một ràng buộc C ++.

Dưới đây là kinh nghiệm của tôi về PyQt và chủ đề.

Tôi khuyến khích bạn sử dụng QThread.


9

Jeff có một số điểm tốt. Chỉ có một luồng chính có thể thực hiện bất kỳ cập nhật GUI nào. Nếu bạn cần cập nhật GUI từ trong luồng, tín hiệu kết nối được xếp hàng của Qt-4 giúp bạn dễ dàng gửi dữ liệu qua các luồng và sẽ tự động được gọi nếu bạn đang sử dụng QThread; Tôi không chắc liệu chúng có phải là nếu bạn đang sử dụng các luồng Python hay không, mặc dù thật dễ dàng để thêm một tham số connect().


5

Tôi thực sự không thể khuyên bạn, nhưng tôi có thể thử mô tả sự khác biệt giữa các luồng CPython và Qt.

Trước hết, các luồng CPython không chạy đồng thời, ít nhất là không phải mã Python. Đúng, họ tạo các luồng hệ thống cho từng luồng Python, tuy nhiên chỉ luồng hiện đang giữ Khóa phiên dịch toàn cầu mới được phép chạy (phần mở rộng C và mã FFI có thể bỏ qua nó, nhưng mã byte Python không được thực thi trong khi luồng không giữ GIL).

Mặt khác, chúng ta có các luồng Qt, là lớp cơ bản phổ biến trên các luồng hệ thống, không có Khóa phiên dịch toàn cầu và do đó có khả năng chạy đồng thời. Tôi không chắc chắn làm thế nào PyQt xử lý nó, tuy nhiên trừ khi các luồng Qt của bạn gọi mã Python, chúng sẽ có thể chạy đồng thời (thanh các khóa bổ sung khác nhau có thể được thực hiện trong các cấu trúc khác nhau).

Để tinh chỉnh thêm, bạn có thể sửa đổi số lượng hướng dẫn mã byte được diễn giải trước khi chuyển quyền sở hữu GIL - giá trị thấp hơn có nghĩa là chuyển đổi ngữ cảnh nhiều hơn (và có thể đáp ứng cao hơn) nhưng hiệu suất thấp hơn cho mỗi luồng riêng lẻ (chuyển đổi ngữ cảnh có chi phí của chúng - nếu bạn hãy thử chuyển đổi mọi hướng dẫn, nó không giúp tăng tốc.)

Hy vọng nó sẽ giúp với vấn đề của bạn :)


7
Điều quan trọng cần lưu ý ở đây: PyQt QThreads thực hiện Khóa phiên dịch toàn cầu . Tất cả mã Python khóa GIL và mọi QThread bạn chạy trong PyQt sẽ chạy mã Python. (Nếu họ không thực sự sử dụng phần "Py" của PyQt :). Nếu bạn chọn trì hoãn từ mã Python đó vào thư viện C bên ngoài, GIL sẽ được phát hành, nhưng điều đó đúng bất kể bạn sử dụng luồng Python hay luồng Qt.
quark

Đó thực sự là những gì tôi đã cố gắng truyền đạt, rằng tất cả mã Python đều có khóa, nhưng mã C / C ++ không chạy trong luồng riêng biệt
p_l

0

Tôi không thể bình luận về sự khác biệt chính xác giữa Python và PyQt đề, nhưng tôi đã làm những gì bạn đang cố gắng để làm bằng QThread, QNetworkAcessManagervà đảm bảo cho cuộc gọi QApplication.processEvents()trong khi thread còn sống. Nếu khả năng đáp ứng GUI thực sự là vấn đề bạn đang cố gắng giải quyết, thì sau này sẽ có ích.


1
QNetworkAcessManagerkhông yêu cầu một chủ đề hoặc processEvents. Nó sử dụng các hoạt động IO không đồng bộ.
Lukáš Lalinský

Rất tiếc ... vâng, tôi đang sử dụng kết hợp QNetworkAcessManagerhttplib2. Mã async của tôi sử dụng httplib2.
brianz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.