ใช้วิธีการเหล่านี้เพื่อทำให้ Python Concurrent Tasks ทำงานได้ดีขึ้น
ปัญหาอยู่ที่ใด
เป็นกรณีที่ประสิทธิภาพแบบมัลติเธรดของ Python ไม่เคยเป็นไปตามความคาดหวังเนื่องจากGIL
ตั้งแต่เวอร์ชัน 3.4 เป็นต้นมา Python ได้เปิดตัวแพ็คเกจ asyncio เพื่อเรียกใช้งาน IO-bound ผ่านการทำงานพร้อมกันพร้อมกัน หลังจากการทำซ้ำหลายครั้ง asyncio APIs ทำงานได้ดีมากและประสิทธิภาพของงานที่ต้องทำพร้อมกันก็ดีขึ้นอย่างมากเมื่อเทียบกับเวอร์ชันแบบมัลติเธรด
อย่างไรก็ตาม ยังมีข้อผิดพลาดมากมายที่โปรแกรมเมอร์ทำเมื่อใช้ asyncio:
ข้อผิดพลาดอย่างหนึ่ง ดังที่แสดงในรูปด้านล่าง คือการใช้วิธี wait coroutine โดยตรงในลักษณะที่เปลี่ยนการเรียกเป็นงานพร้อมกันจากอะซิงโครนัสเป็นซิงโครนัส ทำให้สูญเสียคุณสมบัติการทำงานพร้อมกันในที่สุด
ข้อผิดพลาดอื่นแสดงอยู่ในภาพด้านล่าง แม้ว่าโปรแกรมเมอร์จะตระหนักว่าเขาจำเป็นต้องใช้create_taskเพื่อสร้างงานที่จะดำเนินการในพื้นหลัง อย่างไรก็ตาม วิธีการรองานทีละงานต่อไปนี้จะเปลี่ยนงานที่มีกำหนดเวลาต่างกันเป็นการรอที่เป็นระเบียบ
รหัสนี้จะรอให้task_1ทำงานเสร็จก่อน ไม่ว่าtask_2จะเสร็จก่อน หรือไม่
การทำงานพร้อมกันคืออะไร?
ดังนั้นงานที่เกิดขึ้นพร้อมกันที่แท้จริงคืออะไร? ลองใช้แผนภาพเพื่อแสดง:
ดังที่ไดอะแกรมแสดง กระบวนการที่เกิดขึ้นพร้อมกันควรประกอบด้วยสองส่วน: การเริ่มต้นงานเบื้องหลัง การรวมงานเบื้องหลังกลับไปที่ฟังก์ชันหลักอีกครั้ง และการรับผลลัพธ์
ผู้อ่านส่วนใหญ่จะทราบวิธีใช้create_taskเพื่อเริ่มงานพื้นหลัง วันนี้ ผมจะแนะนำวิธีการบางอย่างในการรอให้งานเบื้องหลังเสร็จสิ้น และแนวทางปฏิบัติที่ดีที่สุดสำหรับแต่ละงาน
เริ่มต้นใช้งาน
ก่อนที่เราจะเริ่มแนะนำตัวละครหลักของวันนี้ เราจำเป็นต้องเตรียมเมธอด async ตัวอย่างเพื่อจำลองการเรียกใช้เมธอด IO รวมถึง AsyncException แบบกำหนดเองที่สามารถใช้เพื่อแจ้งข้อความแสดงข้อยกเว้นเมื่อการทดสอบแสดงข้อยกเว้น:
การเปรียบเทียบวิธีการดำเนินการพร้อมกัน
เมื่อเตรียมการกันเรียบร้อยแล้วก็ได้เวลาออกเดินทางของวันและคาดเข็มขัดนิรภัย
1. asyncio.gather
asyncio.gatherสามารถใช้เพื่อเริ่มชุดของงานเบื้องหลัง รอให้เสร็จสิ้นการดำเนินการ และรับรายการผลลัพธ์:
asyncio.gatherแม้ว่าจะสร้างกลุ่มของงานเบื้องหลัง แต่ก็ไม่สามารถยอมรับรายการหรือคอลเลกชันเป็นอาร์กิวเมนต์ได้โดยตรง หากคุณต้องการผ่านรายการที่มีงานเบื้องหลัง โปรดแกะกล่องออก
asyncio.gatherใช้return_exceptionsเวลาโต้แย้ง เมื่อค่าของreturn_exceptionเป็น False ถ้างานเบื้องหลังส่งข้อยกเว้น มันจะถูกส่งไปยังผู้เรียกวิธีการรวบรวม และรายการผลลัพธ์ของวิธีการรวบรวมว่างเปล่า
เมื่อค่าของreturn_exceptionเป็น True ข้อยกเว้นที่เกิดจากงานเบื้องหลังจะไม่ส่งผลต่อการดำเนินการของงานอื่นๆ และจะถูกรวมเข้ากับรายการผลลัพธ์และส่งกลับรวมกันในที่สุด
results = await asyncio.gather(*aws, return_exceptions=True)
Screenshot. Image by Author
อย่างไรก็ตามgatherไม่สามารถตั้งค่าพารามิเตอร์การหมดเวลาได้โดยตรง หากคุณต้องการกำหนดเวลาสำหรับงานที่กำลังทำงานอยู่ทั้งหมด ให้ใช้ท่านี้ ซึ่งไม่สวยงามเพียงพอ
2. asyncio.as_completed
ในบางครั้ง เราต้องเริ่มดำเนินการต่อไปนี้ทันทีหลังจากเสร็จสิ้นภารกิจเบื้องหลังหนึ่งอย่าง ตัวอย่างเช่น เมื่อเรารวบรวมข้อมูลบางอย่างและเรียกใช้โมเดลแมชชีนเลิร์นนิงสำหรับการคำนวณทันที วิธีgatherการไม่สามารถตอบสนองความต้องการของเรา แต่เราสามารถใช้as_completedวิธีการ นี้ได้
ก่อนใช้asyncio.as_completedวิธี มาดูซอร์สโค้ดของวิธีนี้กันก่อน
# This is *not* a @coroutine! It is just an iterator (yielding Futures).
def as_completed(fs, *, timeout=None):
# ...
for f in todo:
f.add_done_callback(_on_completion)
if todo and timeout is not None:
timeout_handle = loop.call_later(timeout, _on_timeout)
for _ in range(len(todo)):
yield _wait_for_one()
as_completedยอมรับtimeoutอาร์กิวเมนต์และงานที่ทำซ้ำในปัจจุบันหลังจากการหมดเวลาจะเกิดขึ้นasyncio.TimeoutError:
as_completedมีความยืดหยุ่นมากกว่าgatherในแง่ของการจัดการผลลัพธ์ของการดำเนินการงาน แต่เป็นการยากที่จะเพิ่มงานใหม่ไปยังรายการงานเดิมในขณะที่รอ
3. asyncio.รอ
asyncio.waitถูกเรียกในลักษณะเดียวกับas_completedแต่คืนค่าทูเพิลที่มีสองชุด: doneและ ถืองานที่ดำเนินการเสร็จแล้วและถืองานที่ยังคงทำงานอยู่pendingdonepending
asyncio.waitยอมรับreturn_whenพารามิเตอร์ซึ่งสามารถรับค่าที่แจกแจงได้สามค่า:
- เมื่อ
return_whenคือ เก็บงานที่เสร็จasyncio.ALL_COMPLETEDสมบูรณ์doneทั้งหมด และpendingว่างเปล่า - เมื่อ
return_whenคือถืองานที่เสร็จสมบูรณ์ทั้งหมด และถืองานที่ยังคงรันasyncio.FIRST_COMPLETEDอยู่donepending - เมื่อ
return_whenคือจัดเก็บงานที่ส่งข้อยกเว้นและดำเนินการเสร็จสิ้นแล้ว และเก็บงานที่ยังคงทำงานasyncio.FIRST_EXCEPTIONอยู่donepending
4. asyncio.TaskGroup
ใน Python 3.11 asyncio ได้เปิดตัว API ใหม่TaskGroupซึ่งทำให้ Python สามารถรองรับ การ ทำงานพร้อมกันที่มีโครงสร้างได้ อย่างเป็นทางการ คุณสมบัตินี้ช่วยให้คุณจัดการวงจรชีวิตของงานที่ต้องทำพร้อมกันด้วยวิธี Pythonic มากขึ้น เพื่อประโยชน์ของพื้นที่ฉันจะไม่ลงรายละเอียดมากเกินไปที่นี่ แต่ผู้อ่านที่สนใจสามารถอ้างถึงบทความของฉัน:
บทสรุป
บทความนี้แนะนำasyncio.gather, asyncio.as_completedและasyncio.waitAPI และทบทวนasyncio.TaskGroupคุณลักษณะใหม่ที่แนะนำใน Python 3.11
การใช้วิธีจัดการงานพื้นหลังเหล่านี้ตามความต้องการที่แท้จริงสามารถทำให้การเขียนโปรแกรมพร้อมกัน asyncio ของเรามีความยืดหยุ่นมากขึ้น
เนื่องจากประสบการณ์ จึงมีการละเว้นอย่างหลีกเลี่ยงไม่ได้ในการอธิบายบทความนี้ ดังนั้นโปรดอย่าลังเลที่จะแสดงความคิดเห็นในระหว่างกระบวนการอ่าน และฉันจะตอบกลับอย่างแข็งขัน





































![รายการที่เชื่อมโยงคืออะไร? [ส่วนที่ 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)