อะไร Giulio ฝรั่งเศสกล่าวว่าเป็นจริงสำหรับ multithreading กับ multiprocessing ทั่วไป
อย่างไรก็ตาม Python *มีปัญหาเพิ่มเติม: มี Global Interpreter Lock ที่ป้องกันสองเธรดในกระบวนการเดียวกันจากการเรียกใช้โค้ด Python ในเวลาเดียวกัน ซึ่งหมายความว่าหากคุณมี 8 คอร์และเปลี่ยนรหัสของคุณเพื่อใช้ 8 เธรดจะไม่สามารถใช้ CPU 800% และทำงานได้เร็วขึ้น 8 เท่า จะใช้ CPU 100% เดียวกันและทำงานด้วยความเร็วเท่ากัน (ในความเป็นจริงมันจะทำงานช้าลงเล็กน้อยเนื่องจากมีค่าใช้จ่ายเพิ่มเติมจากการทำเธรดแม้ว่าคุณจะไม่มีข้อมูลที่แชร์ก็ตาม แต่ก็ไม่ต้องสนใจสิ่งนั้นในตอนนี้)
มีข้อยกเว้นสำหรับเรื่องนี้ หากการคำนวณที่หนักหน่วงของโค้ดของคุณไม่ได้เกิดขึ้นจริงใน Python แต่ในบางไลบรารีที่มีโค้ด C แบบกำหนดเองที่จัดการ GIL ได้อย่างเหมาะสมเช่นแอพ numpy คุณจะได้รับประโยชน์ด้านประสิทธิภาพที่คาดหวังจากเธรด เช่นเดียวกับกรณีที่การคำนวณหนักทำโดยกระบวนการย่อยบางส่วนที่คุณเรียกใช้และรอ
ที่สำคัญมีบางกรณีที่ไม่สำคัญ ตัวอย่างเช่นเซิร์ฟเวอร์เครือข่ายใช้เวลาส่วนใหญ่ในการอ่านแพ็กเก็ตนอกเครือข่ายและแอป GUI ใช้เวลาส่วนใหญ่ในการรอเหตุการณ์ของผู้ใช้ เหตุผลหนึ่งในการใช้เธรดในเซิร์ฟเวอร์เครือข่ายหรือแอป GUI คือเพื่อให้คุณสามารถทำ "งานพื้นหลัง" ที่ใช้งานได้ยาวนานโดยไม่หยุดเธรดหลักจากการให้บริการแพ็กเก็ตเครือข่ายหรือเหตุการณ์ GUI และใช้งานได้ดีกับเธรด Python (ในทางเทคนิคหมายความว่าเธรด Python ให้การทำงานพร้อมกันแม้ว่าจะไม่ให้ core-parallelism ก็ตาม)
แต่ถ้าคุณกำลังเขียนโปรแกรมที่ผูกกับ CPU ใน Python แท้โดยทั่วไปแล้วการใช้เธรดเพิ่มเติมจะไม่เป็นประโยชน์
การใช้กระบวนการแยกกันไม่มีปัญหาดังกล่าวกับ GIL เนื่องจากแต่ละกระบวนการมี GIL แยกกัน แน่นอนว่าคุณยังคงมีข้อแลกเปลี่ยนระหว่างเธรดและโปรเซสเหมือนกันกับภาษาอื่น ๆ - การแชร์ข้อมูลระหว่างโปรเซสนั้นยากและแพงกว่าระหว่างเธรดการรันโปรเซสจำนวนมากหรือการสร้างและทำลายอาจมีค่าใช้จ่ายสูง บ่อยครั้ง ฯลฯ แต่ GIL ให้ความสำคัญกับความสมดุลของกระบวนการในแบบที่ไม่เป็นความจริงเช่น C หรือ Java ดังนั้นคุณจะพบว่าตัวเองใช้การประมวลผลหลายขั้นตอนใน Python บ่อยกว่าที่คุณใช้ใน C หรือ Java
ในขณะเดียวกันปรัชญา "รวมแบตเตอรี่" ของ Python นำเสนอข่าวดี: การเขียนโค้ดเป็นเรื่องง่ายมากที่สามารถสลับไปมาระหว่างเธรดและกระบวนการต่างๆได้ด้วยการเปลี่ยนแบบซับเดียว
หากคุณออกแบบโค้ดของคุณในรูปแบบของ "งาน" ที่มีอยู่ในตัวซึ่งจะไม่แชร์สิ่งใดกับงานอื่น ๆ (หรือโปรแกรมหลัก) ยกเว้นอินพุตและเอาต์พุตคุณสามารถใช้concurrent.futures
ไลบรารีเพื่อเขียนโค้ดของคุณรอบเธรดพูลดังนี้:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
executor.submit(job, argument)
executor.map(some_function, collection_of_independent_things)
คุณยังสามารถรับผลลัพธ์ของงานเหล่านั้นและส่งต่อไปยังงานต่อไปรอสิ่งต่างๆตามลำดับการดำเนินการหรือตามลำดับความสำเร็จ ฯลฯ อ่านหัวข้อเกี่ยวกับFuture
วัตถุเพื่อดูรายละเอียด
ตอนนี้หากปรากฎว่าโปรแกรมของคุณใช้ CPU 100% อยู่ตลอดเวลาและการเพิ่มเธรดมากขึ้นทำให้ช้าลงแสดงว่าคุณกำลังประสบปัญหา GIL ดังนั้นคุณต้องเปลี่ยนไปใช้กระบวนการ สิ่งที่คุณต้องทำคือเปลี่ยนบรรทัดแรก:
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
ข้อแม้ที่แท้จริงประการเดียวคืออาร์กิวเมนต์และค่าที่ส่งคืนของงานของคุณจะต้องดอง (และไม่ต้องใช้เวลาหรือความทรงจำมากเกินไปในการดอง) เพื่อให้สามารถใช้งานข้ามกระบวนการได้ โดยปกตินี่ไม่ใช่ปัญหา แต่บางครั้งก็เป็นเช่นนั้น
แต่ถ้างานของคุณไม่สามารถอยู่ได้ด้วยตัวเองล่ะ? หากคุณสามารถออกแบบโค้ดของคุณในแง่ของงานที่ส่งผ่านข้อความจากที่หนึ่งไปยังอีกที่หนึ่งได้ก็ยังค่อนข้างง่าย คุณอาจต้องใช้threading.Thread
หรือmultiprocessing.Process
แทนที่จะพึ่งพาสระว่ายน้ำ และคุณจะต้องสร้างqueue.Queue
หรือmultiprocessing.Queue
วัตถุอย่างชัดเจน (มีตัวเลือกอื่น ๆ อีกมากมายเช่นไปป์ซ็อกเก็ตไฟล์ที่มีฝูง ... แต่ประเด็นคือคุณต้องทำบางอย่างด้วยตนเองหากเวทมนตร์อัตโนมัติของ Executor ไม่เพียงพอ)
แต่ถ้าคุณไม่สามารถพึ่งพาการส่งข้อความได้ล่ะ? จะเกิดอะไรขึ้นถ้าคุณต้องการสองงานเพื่อให้ทั้งสองกลายพันธุ์โครงสร้างเดียวกันและดูการเปลี่ยนแปลงของกันและกัน? ในกรณีนี้คุณจะต้องทำการซิงโครไนซ์ด้วยตนเอง (ล็อกเซมาโฟเรสเงื่อนไข ฯลฯ ) และหากคุณต้องการใช้กระบวนการอ็อบเจ็กต์หน่วยความจำที่ใช้ร่วมกันอย่างชัดเจนเพื่อบูต นี่คือเมื่อการประมวลผลแบบมัลติเธรด (หรือการประมวลผลหลายขั้นตอน) ทำได้ยาก หากคุณสามารถหลีกเลี่ยงได้ก็เยี่ยมมาก ถ้าคุณทำไม่ได้คุณจะต้องอ่านมากกว่าที่ใครบางคนจะสามารถตอบ SO ได้
จากความคิดเห็นคุณต้องการทราบความแตกต่างระหว่างเธรดและกระบวนการใน Python จริงๆแล้วถ้าคุณอ่านคำตอบของ Giulio Franco และของฉันและลิงก์ทั้งหมดของเรามันควรจะครอบคลุมทุกอย่าง ... แต่บทสรุปจะมีประโยชน์อย่างแน่นอนดังนั้นต่อไปนี้:
- เธรดแชร์ข้อมูลตามค่าเริ่มต้น กระบวนการทำไม่ได้
- อันเป็นผลมาจาก (1) การส่งข้อมูลระหว่างกระบวนการโดยทั่วไปจำเป็นต้องมีการดองและการแกะออก **
- ผลที่ตามมาอีกประการหนึ่งของ (1) การแบ่งปันข้อมูลโดยตรงระหว่างกระบวนการโดยทั่วไปต้องวางไว้ในรูปแบบระดับต่ำเช่น Value, Array และ
ctypes
ประเภท
- กระบวนการต่างๆไม่อยู่ภายใต้ GIL
- ในบางแพลตฟอร์ม (ส่วนใหญ่เป็น Windows) กระบวนการสร้างและทำลายมีราคาแพงกว่ามาก
- มีข้อ จำกัด พิเศษบางอย่างเกี่ยวกับกระบวนการซึ่งบางอย่างก็แตกต่างกันไปในแต่ละแพลตฟอร์ม ดูหลักเกณฑ์การเขียนโปรแกรมสำหรับรายละเอียด
threading
โมดูลไม่ได้มีบางส่วนของคุณสมบัติของmultiprocessing
โมดูล (คุณสามารถใช้multiprocessing.dummy
เพื่อรับ API ส่วนใหญ่ที่ขาดหายไปที่ด้านบนของเธรดหรือคุณสามารถใช้โมดูลระดับที่สูงขึ้นได้เช่นconcurrent.futures
และไม่ต้องกังวลกับมัน)
* ไม่ใช่ Python จริงภาษาที่มีปัญหานี้ แต่เป็น CPython ซึ่งเป็นการใช้งาน "มาตรฐาน" ของภาษานั้น การใช้งานอื่น ๆ บางอย่างไม่มี GIL เช่น Jython
** หากคุณใช้วิธีfork start สำหรับการประมวลผลหลายขั้นตอนซึ่งคุณสามารถทำได้บนแพลตฟอร์มที่ไม่ใช่ Windows ส่วนใหญ่กระบวนการย่อยแต่ละรายการจะได้รับทรัพยากรใด ๆ ที่ผู้ปกครองมีเมื่อเด็กเริ่มต้นซึ่งอาจเป็นอีกวิธีหนึ่งในการส่งผ่านข้อมูลไปยังเด็ก ๆ
Thread
โมดูล (เรียก_thread
ใน python 3.x) พูดตามตรงฉันไม่เคยเข้าใจความแตกต่างด้วยตัวเองเลย ...