เธรดทำงานอย่างไรใน Python และข้อผิดพลาดทั่วไปของ Python-threading คืออะไร


85

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

จากสิ่งที่ฉันสามารถบอกได้มีเพียงเธรดเดียวเท่านั้นที่สามารถทำงานได้พร้อมกันและเธรดที่ใช้งานจะสลับทุกๆ 10 คำสั่งหรือมากกว่านั้น?

มีคำอธิบายที่ดีตรงไหนหรือให้คำอธิบายได้ไหม นอกจากนี้ยังเป็นการดีมากที่จะทราบถึงปัญหาทั่วไปที่คุณพบขณะใช้เธรดกับ Python

คำตอบ:


51

ใช่เนื่องจาก Global Interpreter Lock (GIL) สามารถรันได้ครั้งละหนึ่งเธรดเท่านั้น ลิงค์ที่มีข้อมูลเชิงลึกเกี่ยวกับเรื่องนี้มีดังนี้

จากลิงค์สุดท้ายคำพูดที่น่าสนใจ:

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

หากคุณต้องการใช้มัลติคอร์pyprocessing จะกำหนด API ตามกระบวนการเพื่อทำการขนานจริง PEPยังรวมถึงมาตรฐานที่น่าสนใจบาง


1
ความคิดเห็นจริงๆเกี่ยวกับใบเสนอราคาของ smoothspan: แน่นอนว่าเธรด Python จำกัด คุณไว้ที่หนึ่งคอร์แม้ว่าเครื่องจะมีหลายตัว อาจมีประโยชน์จากมัลติคอร์เนื่องจากเธรดถัดไปสามารถใช้งานได้โดยไม่ต้องเปลี่ยนบริบท แต่เธรด Python ของคุณไม่สามารถใช้ประโยชน์จาก> 1 คอร์ได้ในครั้งเดียว
James Brady

2
ถูกต้องเธรดไพ ธ อนถูก จำกัด ไว้ที่คอร์เดียวในทางปฏิบัติ UNLESS โมดูล C จะโต้ตอบกับ GIL ได้ดีและรันเธรดเนทีฟของตัวเอง
Arafangion

จริงๆแล้วหลายคอร์ทำให้เธรดมีประสิทธิภาพน้อยลงเนื่องจากมีการปั่นด้ายจำนวนมากโดยตรวจสอบว่าแต่ละเธรดสามารถเข้าถึง GIL ได้หรือไม่ แม้จะใช้ GIL ใหม่ประสิทธิภาพก็ยังแย่ลง ... dabeaz.com/python/NewGIL.pdf
ขั้นพื้นฐาน

2
โปรดทราบว่าข้อพิจารณาของ GIL ที่จะไม่ใช้กับล่ามทั้งหมด เท่าที่ฉันทราบทั้งฟังก์ชัน IronPython และ Jython ที่ไม่มี GIL ทำให้โค้ดของพวกเขาสามารถใช้ฮาร์ดแวร์หลายโปรเซสเซอร์ได้อย่างมีประสิทธิภาพมากขึ้น ดังที่ Arafangion กล่าวไว้ล่าม CPython ยังสามารถรันแบบมัลติเธรดได้อย่างถูกต้องหากรหัสที่ไม่ต้องการเข้าถึงรายการข้อมูล Python คลายการล็อกจากนั้นจึงได้รับอีกครั้งก่อนที่จะกลับมา
holdenweb

อะไรทำให้เกิดการสลับบริบทระหว่างเธรดใน Python มันขึ้นอยู่กับการขัดจังหวะตัวจับเวลาหรือไม่? การบล็อกหรือการเรียกร้องผลตอบแทนที่เฉพาะเจาะจง?
CMCDragonkai

36

Python เป็นภาษาที่ค่อนข้างง่ายในการเชื่อมต่อ แต่มีข้อแม้ สิ่งที่ใหญ่ที่สุดที่คุณต้องรู้คือ Global Interpreter Lock ซึ่งอนุญาตให้มีเธรดเดียวเท่านั้นที่สามารถเข้าถึงล่ามได้ นี่หมายถึงสองสิ่ง: 1) คุณแทบไม่เคยพบว่าตัวเองใช้คำสั่งล็อคใน python และ 2) หากคุณต้องการใช้ประโยชน์จากระบบมัลติโปรเซสเซอร์คุณต้องใช้กระบวนการแยกกัน แก้ไข: ฉันควรชี้ให้เห็นว่าคุณสามารถใส่รหัสบางส่วนใน C / C ++ ได้หากคุณต้องการใช้ GIL ด้วย

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

หากคุณต้องการปรับปรุงการตอบสนองคุณควรพิจารณาโดยใช้เธรด มีทางเลือกอื่น ๆ ที่มี แต่คือmicrothreading นอกจากนี้ยังมีกรอบบางอย่างที่คุณควรพิจารณา:


@JS - คงที่ รายการนั้นล้าสมัยไปแล้ว
Jason Baker

ฉันรู้สึกผิดที่คุณต้องมีกระบวนการหลายอย่าง - ด้วยค่าใช้จ่ายทั้งหมดที่เกี่ยวข้อง - เพื่อใช้ประโยชน์จากระบบมัลติคอร์ เรามีเซิร์ฟเวอร์บางตัวที่มี 32 คอร์แบบลอจิคัล - ดังนั้นฉันจึงต้องการ 32 กระบวนการเพื่อให้ใช้งานได้อย่างมีประสิทธิภาพ? Madness
Basic

@ พื้นฐาน - ค่าใช้จ่ายในการเริ่มต้นกระบวนการเทียบกับการเริ่มต้นเธรดในปัจจุบันมีน้อยมาก ฉันคิดว่าคุณอาจเริ่มเห็นปัญหาหากเรากำลังพูดถึงคำค้นหาหลายพันรายการต่อวินาที แต่ฉันจะตั้งคำถามกับตัวเลือกของ Python สำหรับบริการที่ยุ่งตั้งแต่แรก
Jason Baker

20

ด้านล่างนี้คือตัวอย่างเธรดพื้นฐาน มันจะเกิด 20 เธรด; แต่ละเธรดจะแสดงหมายเลขเธรด เรียกใช้และสังเกตลำดับที่พิมพ์

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

ตามที่คุณได้บอกใบ้เธรด Python จะถูกนำไปใช้ผ่านการแบ่งเวลา นี่คือวิธีที่พวกเขาได้รับเอฟเฟกต์ "ขนาน"

ในตัวอย่างของฉันคลาส Foo ของฉันขยายเธรดจากนั้นฉันก็ใช้runเมธอดซึ่งเป็นที่ที่โค้ดที่คุณต้องการเรียกใช้ในเธรดไป ในการเริ่มต้นเธรดที่คุณเรียกstart()บนวัตถุเธรดซึ่งจะเรียกใช้runเมธอดโดยอัตโนมัติ...

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


10

ใช้เธรดใน python หากผู้ปฏิบัติงานแต่ละคนกำลังดำเนินการผูกมัด I / O หากคุณกำลังพยายามปรับขนาดในหลายคอร์บนเครื่องให้ค้นหาเฟรมเวิร์กIPCที่ดีสำหรับ python หรือเลือกภาษาอื่น


6

หมายเหตุ: ทุกที่ที่ฉันพูดถึงthreadฉันหมายถึงเฉพาะเธรดใน pythonจนกว่าจะระบุไว้อย่างชัดเจน

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

ในฐานะที่จัดการหน่วยความจำในหลามไม่ด้ายปลอดภัยแต่ละหัวข้อต้องมีสิทธิ์พิเศษในการโครงสร้างข้อมูลใน interpreter.This หลามสิทธิ์พิเศษที่ได้มาโดยกลไกที่เรียกว่า(ล็อค interpretr ทั่วโลก)GIL

Why does python use GIL?

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

แนวคิดคือเมื่อใดก็ตามที่เธรดถูกเรียกใช้งาน(แม้ว่าจะเป็นเธรดหลักก็ตาม) GIL จะได้รับและหลังจากช่วงเวลาที่กำหนดไว้ล่วงหน้าบางช่วงเวลา GIL จะถูกปล่อยออกมาโดยเธรดปัจจุบันและเธรดอื่นได้รับกลับมาอีกครั้ง (ถ้ามี)

Why not simply remove GIL?

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

ดังนั้นค่าใช้จ่ายในการลบ GIL จึงได้รับการจ่ายโดยประสิทธิภาพที่ลดลงของแอปพลิเคชันเธรดเดียวซึ่งไม่เป็นที่ต้องการ

So when does thread switching occurs in python?

เธรดสวิทช์เกิดขึ้นเมื่อปล่อย GIL ดังนั้น GIL ​​จะถูกปล่อยเมื่อใด มีสองสถานการณ์ที่ต้องพิจารณา

หากเธรดกำลังดำเนินการกับ CPU Bound (Ex การประมวลผลภาพ)

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

ในเวอร์ชันใหม่แทนที่จะใช้การนับคำสั่งเป็นเมตริกเพื่อสลับเธรดจะใช้ช่วงเวลาที่กำหนดค่าได้ สวิทช์ช่วงเริ่มต้นคือ 5 milliseconds.you sys.getswitchinterval()จะได้รับช่วงเวลาที่สวิทช์ปัจจุบันใช้ สิ่งนี้สามารถเปลี่ยนแปลงได้โดยใช้sys.setswitchinterval()

หากเธรดกำลังดำเนินการ IO Bound Operations (การเข้าถึงระบบไฟล์ Ex หรือ
Network IO)

GIL จะถูกรีลีสเมื่อใดก็ตามที่เธรดกำลังรอให้การดำเนินการ IO เสร็จสมบูรณ์

Which thread to switch to next?

ล่ามไม่มีตัวกำหนดตารางเวลาของตัวเองเธรดใดจะถูกจัดกำหนดการเมื่อสิ้นสุดช่วงเวลาเป็นการตัดสินใจของระบบปฏิบัติการ .


3

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

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


2

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

ฉันจะไปไกลถึงการแนะนำผู้ปกครองหลายคนเกี่ยวกับโปรเซสเซอร์และพยายามให้เหมือนงานบนแกนเดียวกัน

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