ล็อคล่ามสากล (GIL) ใน CPython คืออะไร


244

ล็อคล่ามระดับโลกคืออะไรและทำไมจึงมีปัญหา?

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


3
ชม David Beazleyบอกทุกสิ่งที่คุณอยากรู้เกี่ยวกับ GIL
hughdbrown

1
ต่อไปนี้เป็นบทความ longish ที่พูดถึง GIL และการทำเกลียวใน Python ที่ฉันเขียนกลับมาสักครู่ มันมีรายละเอียดอยู่พอสมควร
jnoller

นี่คือโค้ดบางส่วนที่แสดงให้เห็นถึงผลกระทบของ GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
ฉันคิดว่านี่เป็นคำอธิบายที่ดีที่สุดของ GIL กรุณาอ่าน. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

realpython.com/python-gilฉันพบว่ามีประโยชน์นี้
qwr

คำตอบ:


220

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

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


46
คำตอบที่ดี - โดยทั่วไปแล้วหมายความว่าเธรดใน Python นั้นดีสำหรับการบล็อก I / O เท่านั้น แอปของคุณจะไม่ใช้ CPU เกินกว่า 1 คอร์ของการใช้โปรเซสเซอร์
Ana Betts

8
"ในฐานะนักพัฒนา Python โดยทั่วไปคุณจะไม่เจอ GIL เว้นแต่ว่าคุณจะเขียนส่วนขยาย C" - คุณอาจไม่ทราบว่าสาเหตุของรหัสมัลติเธรดที่ทำงานด้วยความเร็วมากนั้นคือ GIL แต่คุณ ' แน่นอนจะรู้สึกถึงผลกระทบของมัน มันยังทำให้ฉันประหลาดใจที่การใช้ประโยชน์จากเซิร์ฟเวอร์ 32-core ด้วย Python หมายความว่าฉันต้องการกระบวนการที่ 32 ด้วยค่าใช้จ่ายที่เกี่ยวข้องทั้งหมด
พื้นฐาน

6
@ PaulBetts: มันไม่เป็นความจริง ก็มีโอกาสที่ผลการดำเนินงานที่สำคัญรหัสแล้วใช้นามสกุล C ที่สามารถและไม่ปล่อย GIL เช่นregex, lxml, numpyโมดูล Cython อนุญาตให้เผยแพร่ GIL ในรหัสที่กำหนดเองเช่นb2a_bin(data)
jfs

5
@Paul Betts: คุณสามารถรับรหัส CPU ได้มากกว่า 1 รหัสโดยใช้โมดูลมัลติโปรเซสเซอร์ การสร้างหลาย ๆ โพรเซสคือ "น้ำหนักที่หนักกว่า" มากกว่าการสร้างหลายเธรด แต่ถ้าคุณต้องการทำงานให้ขนานกันในไพ ธ อนมันเป็นตัวเลือก
AJNeufeld

1
@david_adler ใช่ยังคงเป็นกรณีและมีแนวโน้มที่จะยังคงอยู่ในขณะนี้ นั่นไม่ได้เป็นการหยุดให้ Python มีประโยชน์สำหรับปริมาณงานที่แตกต่างกัน
Vinay Sajip

59

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

เพื่อนำมาเปรียบเทียบในโลกแห่งความเป็นจริง: ลองนึกภาพนักพัฒนา 100 คนที่ทำงานใน บริษัท ด้วยกาแฟแก้วเดียว นักพัฒนาส่วนใหญ่จะใช้เวลารอกาแฟแทนการเข้ารหัส

สิ่งนี้ไม่เฉพาะเจาะจงกับ Python - ฉันไม่ทราบรายละเอียดว่า Python ต้องการอะไร GIL ตั้งแต่แรก อย่างไรก็ตามหวังว่ามันจะทำให้คุณมีความคิดที่ดีขึ้นเกี่ยวกับแนวคิดทั่วไป


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


36

ก่อนอื่นเรามาทำความเข้าใจกับสิ่งที่ python GIL จัดหาให้:

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

ตอนนี้ทำไมถึงเป็นปัญหา:

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

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

แก้ไข: GIL เป็นรายละเอียดการใช้งานของ CPython IronPython และ Jython ไม่มี GIL ดังนั้นโปรแกรมแบบมัลติเธรดที่แท้จริงควรเป็นไปได้ในพวกเขาคิดว่าฉันไม่เคยใช้ PyPy และ Jython และไม่แน่ใจในเรื่องนี้


4
หมายเหตุ : PyPy มีGIL อ้างอิง : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why ในขณะที่ Ironpython และ Jython ไม่มี GIL
Tasdik Rahman

แท้จริงแล้ว PyPy มี GIL แต่ IronPython ไม่มี
Emmanuel

@Emmanuel แก้ไขคำตอบเพื่อลบ PyPy และรวม IronPython
Akshar Raaj

17

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

https://www.youtube.com/watch?v=ph374fJqFPE

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

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

มัลติเธรดสามารถเอาต์ซอร์ซไปยังระบบปฏิบัติการ (โดยทำการประมวลผลหลายอย่าง), แอปพลิเคชั่นภายนอกบางตัวที่เรียกรหัส Python ของคุณ (เช่น Spark หรือ Hadoop) หรือรหัสบางอย่างที่รหัส Python ของคุณโทรออก (เช่น: รหัสเรียกฟังก์ชั่น C ที่ทำสิ่งที่มีหลายเธรดราคาแพง)


15

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

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

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

โปรดทราบว่ามันเป็นไปได้ที่จะปล่อย GIL ถ้าคุณอยู่ในวิธีที่คุณเขียนใน C เช่น

การใช้ GIL นั้นไม่ได้มีมา แต่กำเนิดจาก Python แต่มีสำหรับล่ามบางตัวรวมถึง CPython ที่พบบ่อยที่สุด (#edited ดูความคิดเห็น)

ปัญหา GIL ยังคงใช้ได้ใน Python 3000


กองซ้อนยังคงมี GIL Stackless ไม่ปรับปรุงเธรด (ในโมดูล) - มันมีวิธีการเขียนโปรแกรมที่แตกต่างกัน (coroutines) ซึ่งพยายามที่จะก้าวไปข้างหน้าปัญหา แต่ต้องการฟังก์ชั่นที่ไม่ปิดกั้น
jnoller

สิ่งที่เกี่ยวกับ GIL ใหม่ใน 3.2?
new123456

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

14

เอกสาร Python 3.7

ฉันอยากจะเน้นคำพูดต่อไปนี้จากเอกสารPythonthreading :

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

ลิงก์นี้ไปยังรายการอภิธานศัพท์global interpreter lockซึ่งอธิบายว่า GIL บอกเป็นนัยว่าการขนานของเธรดใน Python นั้นไม่เหมาะสมสำหรับงานที่เชื่อมโยงกับ CPU :

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

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

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

คำพูดนี้ยังบอกเป็นนัยว่า dicts และการกำหนดตัวแปรจึงปลอดภัยต่อการใช้งานเช่นเดียวกับรายละเอียดการใช้งานของ CPython:

ถัดไปเอกสารสำหรับmultiprocessingแพคเกจอธิบายวิธีเอาชนะ GIL ด้วยกระบวนการวางไข่ในขณะที่เปิดเผยส่วนต่อประสานที่คล้ายกับของthreading:

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

และเอกสารสำหรับconcurrent.futures.ProcessPoolExecutorอธิบายว่ามันใช้multiprocessingเป็นแบ็กเอนด์:

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

ซึ่งควรเปรียบเทียบกับคลาสฐานอื่นThreadPoolExecutorที่ใช้เธรดแทนกระบวนการ

ThreadPoolExecutor เป็นคลาสย่อย Executor ที่ใช้พูลของเธรดเพื่อดำเนินการเรียกแบบอะซิงโครนัส

ซึ่งเราสรุปได้ว่าThreadPoolExecutorเหมาะสำหรับงานที่ถูกผูกไว้ I / O เท่านั้นในขณะที่ProcessPoolExecutorยังสามารถจัดการงานที่ผูกกับ CPU ได้

คำถามต่อไปนี้ถามว่าทำไม GIL ถึงมีอยู่ในสถานที่แรก: ทำไมต้องล่ามแปลภาษาสากล?

การทดลองเทียบกับกระบวนการ

ที่Multiprocessing กับ Threading Pythonฉันได้ทำการวิเคราะห์การทดลองของโพรเซสกับเธรดใน Python

ดูตัวอย่างผลลัพธ์อย่างรวดเร็ว:

ป้อนคำอธิบายรูปภาพที่นี่


0

เหตุใด Python (CPython และอื่น ๆ ) จึงใช้ GIL

จากhttp://wiki.python.org/moin/GlobalInterpreterLock

ใน CPython การล็อคตัวแปลภาษาทั่วโลกหรือ GIL เป็น mutex ที่ป้องกันเธรดหลายเธรดจากการเรียกใช้งาน Python bytecode ในครั้งเดียว การล็อกนี้เป็นสิ่งจำเป็นเนื่องจากการจัดการหน่วยความจำของ CPython ไม่ปลอดภัยสำหรับเธรด

จะลบออกจาก Python ได้อย่างไร

เช่นเดียวกับ Lua บางที Python อาจเริ่ม VM หลายตัว แต่หลามไม่ทำอย่างนั้นฉันเดาว่าควรมีเหตุผลอื่น ๆ

ใน Numpy หรืออื่น ๆ ไพ ธ อนไลบรารี่บางครั้งการปล่อย GIL ไปยังเธรดอื่นสามารถเพิ่มประสิทธิภาพของโปรแกรมทั้งหมด


0

ฉันต้องการแบ่งปันตัวอย่างจากหนังสือมัลติเธรดสำหรับ Visual Effects ดังนั้นนี่คือสถานการณ์การล็อกแบบคลาสสิก

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

ตอนนี้ให้พิจารณาเหตุการณ์ในลำดับที่ทำให้เกิดการล็อคตาย

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    Main Thread                             Other Thread                         
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
 1  Python Command acquires GIL             Work started                         
 2  Computation requested                   MyCallback runs and acquires MyMutex 
 3                                          MyCallback now waits for GIL         
 4  MyCallback runs and waits for MyMutex   waiting for GIL                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.