การทำเธรดในแอปพลิเคชัน PyQt: ใช้เธรด Qt หรือเธรด Python?


118

ฉันกำลังเขียนแอปพลิเคชัน GUI ที่ดึงข้อมูลผ่านการเชื่อมต่อเว็บเป็นประจำ เนื่องจากการดึงข้อมูลนี้ใช้เวลาสักครู่จึงทำให้ UI ไม่ตอบสนองในระหว่างกระบวนการดึงข้อมูล (ไม่สามารถแบ่งออกเป็นส่วนย่อย ๆ ได้) นี่คือเหตุผลที่ฉันต้องการจ้างการเชื่อมต่อเว็บไปยังเธรดผู้ปฏิบัติงานแยกต่างหาก

[ใช่ฉันรู้แล้วตอนนี้ฉันมีปัญหาสองอย่าง]

อย่างไรก็ตามแอปพลิเคชันใช้ PyQt4 ดังนั้นฉันต้องการทราบว่าทางเลือกที่ดีกว่าคืออะไร: ใช้เธรดของ Qt หรือใช้threadingโมดูลPython ? ข้อดี / ข้อเสียของแต่ละข้อคืออะไร? หรือคุณมีข้อเสนอแนะที่แตกต่างไปจากเดิมอย่างสิ้นเชิง?

แก้ไข (รางวัลใหม่):ในขณะที่วิธีแก้ปัญหาในกรณีเฉพาะของฉันอาจจะใช้คำขอเครือข่ายที่ไม่ปิดกั้นเช่นที่Jeff OberและLukáš Lalinsk (แนะนำ (โดยทั่วไปแล้วจะทิ้งปัญหาการทำงานพร้อมกันไปยังการใช้งานเครือข่าย) แต่ฉันก็ยังต้องการมากกว่านี้ คำตอบเชิงลึกสำหรับคำถามทั่วไป:

ข้อดีและข้อเสียของการใช้เธรด PyQt4 (เช่น Qt) บนเธรด Python ดั้งเดิม (จากthreadingโมดูล) คืออะไร?


แก้ไข 2:ขอบคุณทุกคำตอบ แม้ว่าจะไม่มีข้อตกลง 100% แต่ดูเหมือนว่าจะมีความเห็นเป็นเอกฉันท์อย่างกว้างขวางว่าคำตอบคือ "ใช้ Qt" เนื่องจากข้อดีของสิ่งนั้นคือการรวมเข้ากับส่วนที่เหลือของห้องสมุดในขณะที่ไม่มีข้อเสียที่แท้จริง

สำหรับใครก็ตามที่ต้องการเลือกระหว่างการใช้งานเธรดสองแบบฉันขอแนะนำให้พวกเขาอ่านคำตอบทั้งหมดที่ให้ไว้ที่นี่รวมถึงเธรดรายชื่อเมลของ PyQt ที่เจ้าอาวาสลิงก์ไปด้วย

มีหลายคำตอบที่ฉันพิจารณาสำหรับค่าหัว; ในที่สุดฉันก็เลือกเจ้าอาวาสสำหรับการอ้างอิงภายนอกที่เกี่ยวข้องมาก อย่างไรก็ตามเป็นการโทรที่ใกล้ชิด

ขอบคุณอีกครั้ง.

คำตอบ:


107

มีการพูดคุยกันเมื่อไม่นานมานี้ในรายชื่ออีเมล PyQt อ้างถึงความคิดเห็นของ Giovanni Bajo ในหัวข้อ:

ส่วนใหญ่จะเหมือนกัน ความแตกต่างที่สำคัญคือ QThreads รวมเข้ากับ Qt ได้ดีกว่า (สัญญาณ / สล็อตแบบ asynchrnous ห่วงเหตุการณ์ ฯลฯ ) นอกจากนี้คุณไม่สามารถใช้ Qt จากเธรด Python ได้ (เช่นคุณไม่สามารถโพสต์เหตุการณ์ไปยังเธรดหลักผ่าน QApplication.postEvent): คุณต้องมี QThread เพื่อให้ใช้งานได้

หลักการทั่วไปอาจใช้ QThreads หากคุณกำลังจะโต้ตอบกับ Qt และใช้เธรด Python อย่างอื่น

และความคิดเห็นก่อนหน้านี้บางส่วนเกี่ยวกับเรื่องนี้จากผู้เขียนของ PyQt: "ทั้งคู่ต่างก็เป็นตัวตัดการใช้งานเธรดเนทีฟเดียวกัน" และการใช้งานทั้งสองใช้ GIL ในลักษณะเดียวกัน


2
คำตอบที่ดี แต่ฉันคิดว่าคุณควรใช้ปุ่ม blockquote เพื่อแสดงให้ชัดเจนว่าคุณอยู่ที่ไหนในความเป็นจริงไม่ได้สรุป แต่อ้างถึง Giovanni Bajo จากรายชื่อผู้รับจดหมาย :)
c089

2
ฉันสงสัยว่าทำไมคุณไม่สามารถโพสต์เหตุการณ์ในเธรดหลักผ่าน QApplication.postEvent () และต้องการ QThread สำหรับสิ่งนั้น? ฉันคิดว่าฉันเคยเห็นคนทำแล้วมันได้ผล
Trilarion

1
ฉันโทรQCoreApplication.postEventจากเธรด Python ในอัตรา 100 ครั้งต่อวินาทีในแอปพลิเคชันที่ทำงานข้ามแพลตฟอร์มและได้รับการทดสอบเป็นเวลา 1,000 ชั่วโมง ฉันไม่เคยเห็นปัญหาใด ๆ จากสิ่งนั้น ฉันคิดว่ามันใช้ได้ตราบเท่าที่วัตถุปลายทางอยู่ใน MainThread หรือ QThread ฉันยังห่อมันขึ้นมาในห้องสมุดที่ดีดูqtutils
three_pineapples

2
ด้วยลักษณะที่ได้รับการโหวตสูงของคำถามและคำตอบนี้ฉันคิดว่ามันคุ้มค่าที่จะชี้ให้เห็นคำตอบ SO ล่าสุดโดย ekhumoroซึ่งอธิบายรายละเอียดเกี่ยวกับเงื่อนไขที่ปลอดภัยในการใช้วิธีการ Qt บางอย่างจากเธรด Python สิ่งนี้นับรวมกับพฤติกรรมที่สังเกตได้จากตัวเองและ @Trilarion
three_pineapples

33

เธรดของ Python จะง่ายและปลอดภัยกว่าและเนื่องจากเป็นแอปพลิเคชันที่ใช้ I / O จึงสามารถข้าม GIL ได้ ที่กล่าวว่าคุณได้พิจารณา I / O แบบไม่ปิดกั้นโดยใช้ซ็อกเก็ต / เลือกแบบ Twisted หรือ non-block?

แก้ไข: เพิ่มเติมเกี่ยวกับเธรด

เธรด Python

เธรดของ Python คือเธรดระบบ อย่างไรก็ตาม Python ใช้ global interpreter lock (GIL) เพื่อให้แน่ใจว่าล่ามจะเรียกใช้คำสั่งไบต์โค้ดในขนาดที่กำหนดในแต่ละครั้งเท่านั้น โชคดีที่ Python เผยแพร่ GIL ระหว่างการดำเนินการอินพุต / เอาต์พุตทำให้เธรดมีประโยชน์ในการจำลอง I / O ที่ไม่ปิดกั้น

ข้อแม้ที่สำคัญ:สิ่งนี้อาจทำให้เข้าใจผิดได้เนื่องจากจำนวนคำสั่งไบต์โค้ดไม่ตรงกับจำนวนบรรทัดในโปรแกรม แม้แต่การกำหนดเพียงครั้งเดียวก็อาจไม่ใช่ atomic ใน Python ดังนั้นการล็อก mutex จึงจำเป็นสำหรับบล็อกโค้ดใด ๆที่ต้องดำเนินการแบบอะตอมแม้จะใช้ GIL ก็ตาม

เธรด QT

เมื่อ Python หยุดการควบคุมไปยังโมดูลที่คอมไพล์ของบุคคลที่สามมันจะปล่อย GIL มันกลายเป็นความรับผิดชอบของโมดูลในการตรวจสอบความเป็นอะตอมตามที่จำเป็น เมื่อส่งการควบคุมกลับไป Python จะใช้ GIL สิ่งนี้สามารถทำให้การใช้ไลบรารีของบุคคลที่สามร่วมกับเธรดเกิดความสับสน การใช้ไลบรารีเธรดภายนอกทำได้ยากยิ่งขึ้นเนื่องจากเพิ่มความไม่แน่นอนว่าการควบคุมอยู่ในมือของโมดูลและล่ามเมื่อใดและเมื่อใด

เธรด QT ทำงานกับ GIL ที่เผยแพร่ เธรด QT สามารถรันโค้ดไลบรารี QT (และโค้ดโมดูลที่คอมไพล์อื่น ๆ ที่ไม่ได้รับ GIL) พร้อมกัน อย่างไรก็ตามรหัส Python ที่ดำเนินการภายในบริบทของเธรด QT ยังคงได้รับ GIL และตอนนี้คุณต้องจัดการตรรกะสองชุดเพื่อล็อครหัสของคุณ

ในท้ายที่สุดทั้งเธรด QT และเธรด Python เป็นตัวตัดรอบเธรดระบบ เธรด Python ปลอดภัยกว่าเล็กน้อยในการใช้เนื่องจากส่วนที่ไม่ได้เขียนด้วย Python (โดยปริยายโดยใช้ GIL) จะใช้ GIL ไม่ว่าในกรณีใด ๆ (แม้ว่าข้อแม้ข้างต้นจะยังคงมีผลอยู่ก็ตาม)

I / O แบบไม่ปิดกั้น

เธรดเพิ่มความซับซ้อนเป็นพิเศษให้กับแอปพลิเคชันของคุณ โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับปฏิสัมพันธ์ที่ซับซ้อนอยู่แล้วระหว่างตัวแปล Python และโค้ดโมดูลที่คอมไพล์แล้ว ในขณะที่หลายคนพบว่าการเขียนโปรแกรมตามเหตุการณ์ยากที่จะติดตาม I / O ที่อิงตามเหตุการณ์และไม่ปิดกั้นมักจะให้เหตุผลเกี่ยวกับเธรดน้อยกว่ามาก

ด้วย I / O แบบอะซิงโครนัสคุณสามารถมั่นใจได้เสมอว่าสำหรับตัวบอกเล่าที่เปิดแต่ละตัวเส้นทางของการดำเนินการนั้นสอดคล้องกันและเป็นระเบียบ เห็นได้ชัดว่ามีปัญหาที่ต้องได้รับการแก้ไขเช่นจะทำอย่างไรเมื่อโค้ดขึ้นอยู่กับช่องทางที่เปิดเพิ่มเติมขึ้นอยู่กับผลลัพธ์ของรหัสที่จะเรียกเมื่อช่องทางที่เปิดอีกช่องหนึ่งส่งคืนข้อมูล

ทางออกที่ดีอย่างหนึ่งสำหรับ I / O ตามเหตุการณ์และไม่ปิดกั้นคือไลบรารีDieselใหม่ ในขณะนี้ถูก จำกัด ไว้ที่ Linux แต่เร็วเป็นพิเศษและค่อนข้างหรูหรา

นอกจากนี้ยังคุ้มค่ากับเวลาของคุณในการเรียนรู้pyeventซึ่งเป็นเครื่องห่อหุ้มรอบไลบรารีเสรีที่ยอดเยี่ยมซึ่งเป็นกรอบพื้นฐานสำหรับการเขียนโปรแกรมตามเหตุการณ์โดยใช้วิธีการที่เร็วที่สุดสำหรับระบบของคุณ (กำหนดตามเวลารวบรวม)


Re Twisted ฯลฯ : ฉันใช้ไลบรารีของบุคคลที่สามที่ทำสิ่งต่างๆเกี่ยวกับเครือข่ายจริง ฉันต้องการหลีกเลี่ยงการปะติดในนั้น แต่ฉันจะยังคงดูอยู่ขอบคุณ
balpha

2
ไม่มีอะไรข้าม GIL ได้จริง แต่ Python เผยแพร่ GIL ระหว่างการดำเนินการ I / O Python ยังเผยแพร่ GIL เมื่อ 'ส่งต่อ' ไปยังโมดูลที่คอมไพล์ซึ่งมีหน้าที่ในการรับ / ปล่อย GIL ด้วยตนเอง
Jeff Ober

2
การอัปเดตผิดพลาดเพียง รหัส Python ทำงานในลักษณะเดียวกันกับเธรด Python มากกว่าใน QThread คุณได้รับ GIL เมื่อคุณรันโค้ด Python (จากนั้น Python จะจัดการการดำเนินการระหว่างเธรด) คุณจะปล่อยมันเมื่อคุณรันโค้ด C ++ ไม่มีความแตกต่างเลย
LukášLalinský

1
ไม่ประเด็นก็คือไม่ว่าคุณจะสร้างเธรดอย่างไรล่าม Python ไม่สนใจ สิ่งที่ต้องคำนึงถึงก็คือสามารถรับ GIL ได้และหลังจาก X คำแนะนำก็สามารถปล่อย / รับใหม่ได้ ตัวอย่างเช่นคุณสามารถใช้ ctypes เพื่อสร้างการเรียกกลับจากไลบรารี C ซึ่งจะถูกเรียกในเธรดแยกต่างหากและรหัสจะทำงานได้ดีโดยที่ไม่รู้ว่าเป็นเธรดอื่น ไม่มีอะไรพิเศษเกี่ยวกับโมดูลเธรด
LukášLalinský

1
คุณกำลังบอกว่า QThread แตกต่างกันอย่างไรเกี่ยวกับการล็อกและ "คุณต้องจัดการตรรกะสองชุดสำหรับการล็อกรหัสของคุณ" อย่างไร สิ่งที่ฉันกำลังพูดคือมันไม่ได้แตกต่างกันเลย ฉันสามารถใช้ ctypes และ pthread_create เพื่อเริ่มเธรดและมันจะทำงานในลักษณะเดียวกัน โค้ด Python ไม่ต้องสนใจ GIL
LukášLalinský

21

ข้อดีของQThreadคือมันรวมเข้ากับไลบรารี Qt ที่เหลือ นั่นคือวิธีการด้ายตระหนักใน Qt จะต้องรู้ในการที่พวกเขาทำงานด้ายและย้ายวัตถุระหว่างกระทู้, QThreadคุณจะต้องใช้ คุณลักษณะที่มีประโยชน์อีกประการหนึ่งคือการเรียกใช้ห่วงเหตุการณ์ของคุณเองในเธรด

หากคุณกำลังเข้าถึงเซิร์ฟเวอร์ HTTP QNetworkAccessManagerที่คุณควรพิจารณา


1
นอกเหนือจากสิ่งที่ฉันแสดงความคิดเห็นเกี่ยวกับคำตอบของ Jeff Ober แล้วก็QNetworkAccessManagerดูมีแนวโน้มดี ขอบคุณ
balpha

14

ผมถามตัวเองคำถามเดียวกันเมื่อผมทำงานเพื่อPyTalk

หากคุณใช้ Qt คุณต้องใช้QThreadเพื่อให้สามารถใช้กรอบงาน Qt และระบบสัญญาณ / สล็อตโดยเฉพาะ

ด้วยเอ็นจิ้นสัญญาณ / สล็อตคุณจะสามารถพูดคุยจากเธรดไปยังอีกเธรดหนึ่งและกับทุกส่วนของโปรเจ็กต์

ยิ่งไปกว่านั้นไม่มีคำถามเกี่ยวกับประสิทธิภาพมากนักเกี่ยวกับตัวเลือกนี้เนื่องจากทั้งคู่เป็นการผูก C ++

นี่คือประสบการณ์ของฉันเกี่ยวกับ PyQt และเธรด

QThreadผมขอแนะนำให้คุณใช้


9

เจฟมีจุดดีบางอย่าง เธรดหลักเพียงชุดเดียวเท่านั้นที่สามารถอัปเดต GUI ได้ หากคุณต้องการอัปเดต GUI จากภายในเธรดสัญญาณการเชื่อมต่อที่อยู่ในคิวของ Qt-4 ทำให้ง่ายต่อการส่งข้อมูลข้ามเธรดและจะเรียกใช้โดยอัตโนมัติหากคุณใช้ QThread connect()ฉันไม่แน่ใจว่าพวกเขาจะเป็นอย่างไรถ้าคุณกำลังใช้หัวข้อหลามแม้ว่ามันจะเป็นเรื่องง่ายที่จะเพิ่มพารามิเตอร์เพื่อ


5

ฉันไม่สามารถแนะนำได้เช่นกัน แต่ฉันสามารถลองอธิบายความแตกต่างระหว่างเธรด CPython และ Qt ได้

ก่อนอื่นเธรด CPython ไม่ทำงานพร้อมกันอย่างน้อยก็ไม่ใช่โค้ด Python ใช่พวกเขาสร้างเธรดระบบสำหรับเธรด Python แต่ละเธรดอย่างไรก็ตามเฉพาะเธรดที่มี Global Interpreter Lock เท่านั้นที่ได้รับอนุญาตให้ทำงาน (ส่วนขยาย C และโค้ด FFI อาจข้ามได้ แต่ Python bytecode จะไม่ทำงานในขณะที่เธรดไม่ได้ถือ GIL)

ในทางกลับกันเรามีเธรด Qt ซึ่งโดยทั่วไปแล้วเป็นเลเยอร์ทั่วไปบนเธรดของระบบไม่มี Global Interpreter Lock ดังนั้นจึงสามารถทำงานพร้อมกันได้ ฉันไม่แน่ใจว่า PyQt จัดการกับมันอย่างไรเว้นแต่เธรด Qt ของคุณจะเรียกรหัส Python พวกเขาควรจะสามารถทำงานพร้อมกันได้ (บาร์ล็อกพิเศษต่างๆที่อาจนำไปใช้ในโครงสร้างต่างๆ)

สำหรับการปรับแต่งเพิ่มเติมคุณสามารถปรับเปลี่ยนจำนวนคำสั่ง bytecode ที่ตีความก่อนที่จะเปลี่ยนการเป็นเจ้าของ GIL - ค่าที่ต่ำกว่าหมายถึงการสลับบริบทมากขึ้น (และอาจตอบสนองได้สูงขึ้น) แต่ประสิทธิภาพที่ต่ำลงต่อแต่ละเธรด (สวิตช์บริบทมีค่าใช้จ่าย - หากคุณ ลองเปลี่ยนคำสั่งทุกสองสามคำก็ไม่ช่วยเพิ่มความเร็ว)

หวังว่าจะช่วยแก้ปัญหาของคุณ :)


7
มันเป็นสิ่งสำคัญที่จะต้องทราบที่นี่: QThreads PyQt ทำใช้ Global ล่ามล็อค รหัส Python ทั้งหมดจะล็อก GIL และ QThreads ใด ๆ ที่คุณเรียกใช้ใน PyQt จะเรียกใช้รหัส Python (หากไม่มีคุณไม่ได้ใช้ส่วน "Py" ของ PyQt :) หากคุณเลือกที่จะเลื่อนจากโค้ด Python นั้นไปยังไลบรารี C ภายนอก GIL จะถูกปล่อยออกมา แต่จะเป็นจริงไม่ว่าคุณจะใช้เธรด Python หรือเธรด Qt ก็ตาม
ควาร์ก

นั่นคือสิ่งที่ฉันพยายามจะสื่อจริงๆว่ารหัส Python ทั้งหมดใช้การล็อก แต่มันไม่สำคัญสำหรับรหัส C / C ++ ที่ทำงานในเธรดแยกต่างหาก
p_l

0

ฉันไม่สามารถแสดงความคิดเห็นในความแตกต่างแน่นอนระหว่างงูหลามและ PyQt หัวข้อ แต่ฉันได้ทำสิ่งที่คุณกำลังพยายามที่จะทำโดยใช้QThread, QNetworkAcessManagerและทำให้แน่ใจว่าการเรียกร้องQApplication.processEvents()ในขณะที่ด้ายยังมีชีวิตอยู่ หากการตอบสนองต่อ GUI เป็นปัญหาที่คุณกำลังพยายามแก้ไขปัญหาในภายหลังจะช่วยได้


1
QNetworkAcessManagerไม่ต้องใช้เธรดหรือprocessEvents. ใช้การดำเนินการ IO แบบอะซิงโครนัส
LukášLalinský

อ๊ะ ... ใช่ฉันใช้การรวมกันของQNetworkAcessManagerและhttplib2. httplib2ฉันใช้รหัส async
brianz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.