Python รองรับมัลติเธรดหรือไม่ สามารถเร่งเวลาดำเนินการได้หรือไม่?


99

ฉันสับสนเล็กน้อยว่ามัลติเธรดทำงานใน Python ได้หรือไม่

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

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

อัปเดต:

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

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

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

ฉันยังรู้เกี่ยวกับโมดูลการประมวลผลหลายขั้นตอน แต่ความสนใจหลักของฉันในตอนนี้คือการโหลดงานขนาดเล็กถึงกลาง (10-30 วินาที) ดังนั้นฉันคิดว่าการทำงานแบบมัลติเธรดจะเหมาะสมกว่าเนื่องจากกระบวนการย่อยอาจช้าในการเริ่มต้น


4
นี่เป็นคำถามที่ค่อนข้างโหลด ฉันคิดว่าคำตอบอยู่ในสิ่งที่คุณต้องการให้เธรดทำ ภายใต้สถานการณ์ส่วนใหญ่ GIL จะป้องกันไม่ให้เธรดมากกว่า 1 ชุดทำงานพร้อมกัน อย่างไรก็ตามมีบางกรณีที่ GIL ถูกปล่อยออกมา (เช่นการอ่านจากไฟล์) เพื่อให้สามารถทำควบคู่กันได้ โปรดทราบว่า GIL เป็นรายละเอียดการใช้งาน Cpython (การใช้งานที่พบบ่อยที่สุด) ไม่มีการใช้งาน python อื่น ๆ (Jython, PyPy ฯลฯ ) มี GIL (AFAIK)
mgilson

2
@mgilson PyPy มี GIL

2
@delnan - ดูเหมือนว่าคุณจะถูกต้อง ขอบคุณ.
mgilson

1
"กระบวนการย่อยอาจช้าในการเริ่มต้น" - คุณสามารถสร้างกลุ่มงานพร้อมที่จะดำเนินการได้ ค่าโสหุ้ยสามารถ จำกัด ได้โดยประมาณระยะเวลาที่ใช้ในการทำให้เป็นอนุกรม / แยกสายข้อมูลที่จำเป็นเพื่อให้งานเริ่มทำงานได้
Brian Cain

1
@KarimBahgat นั่นคือสิ่งที่ฉันหมายถึง
Brian Cain

คำตอบ:


139

GIL ไม่ป้องกันเธรด GIL ทั้งหมดทำคือตรวจสอบให้แน่ใจว่ามีเธรดเดียวเท่านั้นที่รันโค้ด Python ในแต่ละครั้ง การควบคุมยังคงสลับระหว่างเธรด

สิ่งที่ GIL ป้องกันคือการใช้ CPU มากกว่าหนึ่งคอร์หรือ CPU แยกกันเพื่อรันเธรดแบบขนาน

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

จากนั้นการปรับใช้เซิร์ฟเวอร์จำนวนมากจะทำอย่างไรคือเรียกใช้กระบวนการ Python มากกว่าหนึ่งกระบวนการเพื่อให้ระบบปฏิบัติการจัดการกับการกำหนดเวลาระหว่างกระบวนการต่างๆเพื่อใช้แกน CPU ของคุณให้สูงสุด คุณยังสามารถใช้multiprocessingไลบรารีเพื่อจัดการการประมวลผลแบบขนานในหลายกระบวนการจากโค้ดเบสเดียวและกระบวนการพาเรนต์ได้หากเหมาะสมกับกรณีการใช้งานของคุณ

โปรดทราบว่า GIL ใช้ได้กับการใช้งาน CPython เท่านั้น Jython และ IronPython ใช้การใช้เธรดที่แตกต่างกัน (เธรดรันไทม์ทั่วไปของ Java VM และ. NET ตามลำดับ)

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

เธรด Python เหมาะอย่างยิ่งสำหรับการสร้าง GUI ที่ตอบสนองหรือสำหรับการจัดการคำขอเว็บสั้น ๆ หลายรายการโดยที่ I / O เป็นคอขวดมากกว่าโค้ด Python ไม่เหมาะสำหรับการขนานโค้ด Python ที่เน้นการคำนวณแบบขนานติดกับmultiprocessingโมดูลสำหรับงานดังกล่าวหรือมอบหมายให้กับไลบรารีภายนอกเฉพาะ


ขอบคุณ @MartijnPieters ฉันมีคำตอบที่ชัดเจนกว่าสำหรับคำถามของฉันว่าสามารถใช้เธรดเพื่อเร่งความเร็วโค้ดเช่น for-loop ซึ่ง "ไม่" ได้หรือไม่ บางทีคุณหรือใครบางคนอาจเขียนคำตอบใหม่ที่ฉันสามารถยอมรับได้ซึ่งมีตัวอย่างเฉพาะบางส่วนของโมดูล / รหัส / การดำเนินการทั่วไปที่ GIL อนุญาตให้เธรดรันพาราเรลและเร็วขึ้น (เช่นตัวอย่างของ I / O และเครือข่าย / การดำเนินการอ่านซ็อกเก็ตที่กล่าวถึงและกรณีอื่น ๆ ที่มีประโยชน์หลายเธรดใน Python) อาจเป็นรายการที่ดีของการใช้งานมัลติเธรดทั่วไปและตัวอย่างการเขียนโปรแกรมบางอย่างหากเป็นไปได้?
Karim Bahgat

4
ไม่ฉันไม่คิดว่าคำตอบดังกล่าวจะมีประโยชน์มากนัก จะซื่อสัตย์ คุณไม่สามารถสร้างรายการที่ละเอียดถี่ถ้วนได้ แต่กฎง่ายๆคือ I / O (การอ่านและเขียนไฟล์ซ็อกเก็ตเครือข่ายท่อ) ถูกจัดการใน C และไลบรารี C จำนวนมากยังปล่อย GIL สำหรับพวกเขา การดำเนินการ แต่ขึ้นอยู่กับไลบรารีที่จะจัดทำเอกสารนี้ให้คุณ
Martijn Pieters

1
ฉันไม่ดีฉันไม่เห็นคำตอบที่อัปเดตของคุณจนถึงตอนนี้ซึ่งคุณได้ให้ตัวอย่างการใช้เธรดที่ดี สิ่งเหล่านี้รวม(แก้ไขฉันถ้าฉันผิด)การเขียนโปรแกรมเครือข่าย (เช่นurllib.urlopen()?) เพื่อเรียกสคริปต์ Python หนึ่งสคริปต์จากภายใน Python GUI และเรียกการดำเนินการหลาย PIL (เช่นImage.transform()) และ numpy (เช่นnumpy.array()) ด้วยเธรด และคุณได้ให้ตัวอย่างเพิ่มเติมในความคิดเห็นของคุณเช่นการใช้หลายเธรดเพื่ออ่านไฟล์ (เช่นf.read()?) ฉันรู้ว่ารายการที่ละเอียดถี่ถ้วนเป็นไปไม่ได้เพียงแค่ต้องการประเภทของตัวอย่างที่คุณให้ไว้ในการอัปเดตของคุณ ไม่ว่าจะด้วยวิธีใดก็ยอมรับคำตอบของคุณ :)
Karim Bahgat

2
@KarimBahgat: ใช่urllib.urlopen()จะเรียกใช้ซ็อกเก็ตเครือข่ายการรอซ็อกเก็ต I / O เป็นโอกาสที่ดีในการสลับเธรดและทำอย่างอื่น
Martijn Pieters

4
แม้ว่าจะไม่เกี่ยวข้องโดยตรงกับปัญหานี้ แต่ก็น่าสังเกตว่าบางครั้งเธรดไม่ได้เกี่ยวกับประสิทธิภาพเลย มันอาจจะง่ายกว่าในการเขียนโค้ดของคุณเป็นเธรดการเรียกใช้งานอิสระหลาย ๆ เธรด ตัวอย่างเช่นคุณอาจมีเธรดหนึ่งเธรดที่เล่นเพลงพื้นหลังหนึ่งเธรดที่ให้บริการ UI และอีกเธรดหนึ่งกำลังใช้งานการคำนวณที่ต้องทำในที่สุด แต่ก็ไม่ได้เร่งรีบ การพยายามจัดลำดับการเล่นบัฟเฟอร์เสียงถัดไปด้วย UI runloop หรือแยกย่อยการคำนวณของคุณเป็นชิ้นเล็ก ๆ พอที่จะไม่รบกวนการโต้ตอบอาจทำได้ยากกว่าการใช้เธรด
ยกเลิก

4

ใช่. :)

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

อ้างจากเอกสาร :

ใน CPython เนื่องจาก Global Interpreter Lock เธรดเดียวเท่านั้นที่สามารถรันโค้ด Python ได้พร้อมกัน (แม้ว่าไลบรารีที่เน้นประสิทธิภาพบางอย่างอาจเอาชนะข้อ จำกัด นี้ได้) หากคุณต้องการให้แอปพลิเคชันของคุณใช้ประโยชน์จากทรัพยากรในการคำนวณของเครื่องมัลติคอร์ได้ดีขึ้นขอแนะนำให้ใช้การประมวลผลหลายขั้นตอน อย่างไรก็ตามเธรดยังคงเป็นโมเดลที่เหมาะสมหากคุณต้องการรันงาน I / O-bound หลายงานพร้อมกัน


3

อนุญาตให้ใช้เธรดใน Python ปัญหาเดียวคือ GIL จะตรวจสอบให้แน่ใจว่ามีการดำเนินการเธรดเพียงครั้งละหนึ่งเธรด (ไม่มีการขนานกัน)

ดังนั้นโดยพื้นฐานแล้วถ้าคุณต้องการมัลติเธรดโค้ดเพื่อเร่งการคำนวณมันจะไม่เร่งความเร็วเนื่องจากเธรดเดียวถูกดำเนินการในแต่ละครั้ง แต่ถ้าคุณใช้มันเพื่อโต้ตอบกับฐานข้อมูลเช่นมันจะ


0

ฉันรู้สึกชอบโปสเตอร์เพราะคำตอบคือ "มันขึ้นอยู่กับว่าคุณต้องการทำอะไร" อย่างไรก็ตามการเร่งความเร็วแบบขนานใน python นั้นแย่มากในประสบการณ์ของฉันแม้ในการประมวลผลหลายขั้นตอน

ตัวอย่างเช่นตรวจสอบบทช่วยสอนนี้ (ผลลัพธ์อันดับสองขึ้นไปใน Google): https://www.machinelearningplus.com/python/parallel-processing-python/

ฉันใส่การกำหนดเวลารอบรหัสนี้และเพิ่มจำนวนกระบวนการ (2,4,8,16) สำหรับฟังก์ชันแผนที่พูลและได้รับการกำหนดเวลาที่ไม่ดีดังต่อไปนี้:

serial 70.8921644706279 
parallel 93.49704207479954 tasks 2
parallel 56.02441442012787 tasks 4
parallel 51.026168536394835 tasks 8
parallel 39.18044807203114 tasks 16

code: # เพิ่มขนาดอาร์เรย์เมื่อเริ่มต้น # โหนดคอมพิวท์ของฉันมี 40 CPU ดังนั้นฉันจึงมีเหลือเฟือที่นี่

arr = np.random.randint(0, 10, size=[2000000, 600])
.... more code ....
tasks = [2,4,8,16]

for task in tasks:
    tic = time.perf_counter()
    pool = mp.Pool(task)

    results = pool.map(howmany_within_range_rowonly, [row for row in data])

    pool.close()
    toc = time.perf_counter()
    time1 = toc - tic
    print(f"parallel {time1} tasks {task}")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.