มัลติเธรดใน Python พร้อมตัวอย่าง: เรียนรู้ GIL ใน Python
กระทู้คืออะไร?
เธรดเป็นหน่วยของการดำเนินการกับการเขียนโปรแกรมพร้อมกัน มัลติเธรดเป็นเทคนิคที่ช่วยให้ CPU สามารถรันงานหลายงานจากกระบวนการเดียวในเวลาเดียวกัน เธรดเหล่านี้สามารถดำเนินการทีละรายการในขณะที่แบ่งปันทรัพยากรกระบวนการของตน
กระบวนการคืออะไร?
กระบวนการคือโปรแกรมที่กำลังดำเนินการ เมื่อคุณเริ่มแอปพลิเคชันในคอมพิวเตอร์ของคุณ (เช่น เบราว์เซอร์หรือโปรแกรมแก้ไขข้อความ) ระบบปฏิบัติการจะสร้าง กระบวนการ
มัลติเธรดคืออะไร Python?
มัลติเธรดใน Python การเขียนโปรแกรมเป็นเทคนิคที่รู้จักกันดีซึ่งหลายเธรดในกระบวนการแบ่งปันพื้นที่ข้อมูลกับเธรดหลัก ซึ่งทำให้การแบ่งปันข้อมูลและการสื่อสารภายในเธรดนั้นง่ายและมีประสิทธิภาพ เธรดมีน้ำหนักเบากว่ากระบวนการ มัลติเธรดอาจดำเนินการทีละรายการในขณะที่แบ่งปันทรัพยากรกระบวนการของตน วัตถุประสงค์ของมัลติเธรดคือการรันหลายงานและเซลล์ฟังก์ชันพร้อมกัน
มัลติโปรเซสเซอร์คืออะไร?
การประมวลผลหลายขั้นตอน ช่วยให้คุณสามารถรันกระบวนการที่ไม่เกี่ยวข้องหลายกระบวนการพร้อมกันได้ กระบวนการเหล่านี้ไม่แบ่งปันทรัพยากรและสื่อสารกันผ่าน IPC
Python มัลติเธรดกับมัลติโปรเซสเซอร์
เพื่อทำความเข้าใจกระบวนการและเธรด ให้พิจารณาสถานการณ์สมมตินี้: แฟ้ม .exe บนคอมพิวเตอร์ของคุณคือโปรแกรม เมื่อคุณเปิดมัน ระบบปฏิบัติการจะโหลดมันลงในหน่วยความจำ และ CPU จะดำเนินการมัน อินสแตนซ์ของโปรแกรมที่กำลังทำงานอยู่เรียกว่ากระบวนการ
ทุกกระบวนการจะมีองค์ประกอบพื้นฐาน 2 ส่วน คือ
- รหัส
- ข้อมูล
ตอนนี้กระบวนการสามารถมีส่วนย่อยหนึ่งหรือหลายส่วนที่เรียกว่า หัวข้อ สิ่งนี้ขึ้นอยู่กับสถาปัตยกรรมระบบปฏิบัติการ คุณสามารถคิดว่าเธรดเป็นส่วนหนึ่งของกระบวนการซึ่งสามารถดำเนินการแยกจากกันได้โดยระบบปฏิบัติการ
กล่าวอีกนัยหนึ่ง มันเป็นกระแสของคำสั่งที่สามารถรันได้อย่างอิสระโดยระบบปฏิบัติการ เธรดภายในกระบวนการเดียวจะแชร์ข้อมูลของกระบวนการนั้นและได้รับการออกแบบให้ทำงานร่วมกันเพื่ออำนวยความสะดวกในการขนาน
เหตุใดจึงต้องใช้มัลติเธรด?
มัลติเธรดช่วยให้คุณแบ่งแอปพลิเคชันออกเป็นหลายงานย่อยและรันงานเหล่านี้พร้อมกันได้ หากคุณใช้มัลติเธรดอย่างเหมาะสม ความเร็ว ประสิทธิภาพ และการเรนเดอร์ของแอปพลิเคชันของคุณก็จะดีขึ้น
Python มัลติเธรด
Python รองรับโครงสร้างสำหรับการประมวลผลแบบหลายตัวและแบบหลายเธรด ในบทช่วยสอนนี้ คุณจะเน้นไปที่การใช้งานเป็นหลัก มัลติเธรด แอปพลิเคชันที่ใช้ Python มีโมดูลหลักสองโมดูลที่ใช้จัดการเธรดได้ Python:
- เทศกาล ด้าย โมดูลและ
- เทศกาล เกลียว โมดูล
อย่างไรก็ตาม ใน python ยังมีสิ่งที่เรียกว่า global reaperter lock (GIL) อีกด้วย ไม่อนุญาตให้ได้รับประสิทธิภาพมากนักและอาจเป็นไปได้ด้วยซ้ำ ลด ประสิทธิภาพของแอพพลิเคชั่นแบบมัลติเธรดบางตัว คุณจะได้เรียนรู้ทั้งหมดเกี่ยวกับเรื่องนี้ในส่วนต่อๆ ไปของบทช่วยสอนนี้
โมดูลเธรดและเธรด
สองโมดูลที่คุณจะได้เรียนรู้ในบทช่วยสอนนี้คือ โมดูลเธรด และ โมดูลเกลียว.
อย่างไรก็ตาม โมดูลเธรดเลิกใช้มานานแล้ว เริ่มด้วย Python 3 ได้รับการกำหนดให้ล้าสมัยและเข้าถึงได้เฉพาะในชื่อเท่านั้น __เกลียว สำหรับความเข้ากันได้ย้อนหลัง
คุณควรใช้ระดับที่สูงกว่า เกลียว โมดูลสำหรับแอปพลิเคชันที่คุณตั้งใจจะปรับใช้ โมดูลเธรดได้รับการกล่าวถึงที่นี่เพื่อวัตถุประสงค์ทางการศึกษาเท่านั้น
โมดูลเธรด
ไวยากรณ์ในการสร้างเธรดใหม่โดยใช้โมดูลนี้เป็นดังนี้:
thread.start_new_thread(function_name, arguments)
เอาล่ะ คุณได้พูดถึงทฤษฎีพื้นฐานในการเริ่มเขียนโค้ดแล้ว ดังนั้นเปิดของคุณ IDLE หรือสมุดบันทึกและพิมพ์ดังต่อไปนี้:
import time import _thread def thread_test(name, wait): i = 0 while i <= 3: time.sleep(wait) print("Running %s\n" %name) i = i + 1 print("%s has finished execution" %name) if __name__ == "__main__": _thread.start_new_thread(thread_test, ("First Thread", 1)) _thread.start_new_thread(thread_test, ("Second Thread", 2)) _thread.start_new_thread(thread_test, ("Third Thread", 3))
บันทึกไฟล์และกด F5 เพื่อรันโปรแกรม หากทุกอย่างถูกต้อง นี่คือผลลัพธ์ที่คุณควรเห็น:
คุณจะได้เรียนรู้เพิ่มเติมเกี่ยวกับสภาพการแข่งขันและวิธีจัดการในหัวข้อต่อๆ ไป
คำอธิบายรหัส
- คำสั่งเหล่านี้นำเข้าโมดูลเวลาและเธรดซึ่งใช้เพื่อจัดการการดำเนินการและการหน่วงเวลาของ Python หัวข้อ
- ที่นี่คุณได้กำหนดฟังก์ชันที่เรียกว่า เธรด_ทดสอบ, ซึ่งจะถูกเรียกโดย start_new_thread วิธี. ฟังก์ชันนี้จะรัน while loop สี่รอบและพิมพ์ชื่อของเธรดที่เรียกว่าเธรดนั้น เมื่อการวนซ้ำเสร็จสิ้น ระบบจะพิมพ์ข้อความแจ้งว่าเธรดเสร็จสิ้นการดำเนินการแล้ว
- นี่คือส่วนหลักของโปรแกรมของคุณ ที่นี่คุณเพียงแค่โทรหา start_new_thread วิธีการกับ thread_test ทำหน้าที่เป็นอาร์กิวเมนต์ ซึ่งจะสร้างเธรดใหม่สำหรับฟังก์ชันที่คุณส่งเป็นอาร์กิวเมนต์ และเริ่มดำเนินการ โปรดทราบว่าคุณสามารถแทนที่สิ่งนี้ได้ (thread_test) กับฟังก์ชันอื่นๆ ที่คุณต้องการเรียกใช้เป็นเธรด
โมดูลการทำเกลียว
โมดูลนี้เป็นการใช้งานเธรดใน python ระดับสูงและเป็นมาตรฐานโดยพฤตินัยสำหรับการจัดการแอปพลิเคชันแบบมัลติเธรด มีคุณสมบัติที่หลากหลายเมื่อเปรียบเทียบกับโมดูลเธรด
นี่คือรายการฟังก์ชันที่มีประโยชน์บางส่วนที่กำหนดไว้ในโมดูลนี้:
ชื่อฟังก์ชั่น | Descriptไอออน |
---|---|
ใช้งานนับ() | ส่งคืนค่าการนับของ ด้าย วัตถุที่ยังมีชีวิตอยู่ |
ปัจจุบันกระทู้() | ส่งกลับวัตถุปัจจุบันของคลาสเธรด |
การระบุ () | แสดงรายการวัตถุ Thread ที่ใช้งานอยู่ทั้งหมด |
isDaemon() | คืนค่าเป็นจริงหากเธรดเป็น daemon |
ยังมีชีวิตอยู่() | คืนค่าเป็นจริงหากเธรดยังมีชีวิตอยู่ |
วิธีการคลาสเธรด | |
เริ่ม () | เริ่มต้นกิจกรรมของเธรด จะต้องถูกเรียกเพียงครั้งเดียวสำหรับแต่ละเธรด เนื่องจากจะทำให้เกิดข้อผิดพลาดรันไทม์หากถูกเรียกหลายครั้ง |
วิ่ง() | เมธอดนี้แสดงถึงกิจกรรมของเธรดและสามารถแทนที่ได้โดยคลาสที่ขยายคลาสเธรด |
เข้าร่วม () | มันจะบล็อกการดำเนินการของโค้ดอื่น ๆ จนกระทั่งเธรดที่เมธอด join() ถูกเรียกถูกยกเลิก |
เรื่องราวเบื้องหลัง: คลาสเธรด
ก่อนที่จะเริ่มเขียนโค้ดโปรแกรมมัลติเธรดโดยใช้โมดูลเธรด สิ่งสำคัญคือต้องเข้าใจเกี่ยวกับคลาส Thread คลาส Thread เป็นคลาสหลักที่กำหนดเทมเพลตและการดำเนินการของเธรดใน Python
วิธีทั่วไปที่สุดในการสร้างแอปพลิเคชันหลามแบบมัลติเธรดคือการประกาศคลาสที่ขยายคลาส Thread และแทนที่เมธอด run()
โดยสรุปคลาส Thread หมายถึงลำดับโค้ดที่ทำงานแยกกัน ด้าย ของการควบคุม
ดังนั้นเมื่อเขียนแอปแบบมัลติเธรด คุณจะต้องทำสิ่งต่อไปนี้:
- กำหนดคลาสที่ขยายคลาสเธรด
- แทนที่ __ในนั้น__ นวกรรมิก
- แทนที่ วิ่ง() วิธี
เมื่อสร้างวัตถุเธรดแล้ว เริ่ม () สามารถใช้วิธีการเพื่อเริ่มดำเนินกิจกรรมนี้และ เข้าร่วม () สามารถใช้เมธอดนี้เพื่อบล็อกโค้ดอื่นๆ ทั้งหมดได้จนกว่ากิจกรรมปัจจุบันจะเสร็จสิ้น
ตอนนี้ เรามาลองใช้โมดูลเธรดเพื่อนำตัวอย่างก่อนหน้าของคุณไปใช้ อีกครั้งยิงของคุณ IDLE และพิมพ์ตามนี้:
import time import threading class threadtester (threading.Thread): def __init__(self, id, name, i): threading.Thread.__init__(self) self.id = id self.name = name self.i = i def run(self): thread_test(self.name, self.i, 5) print ("%s has finished execution " %self.name) def thread_test(name, wait, i): while i: time.sleep(wait) print ("Running %s \n" %name) i = i - 1 if __name__=="__main__": thread1 = threadtester(1, "First Thread", 1) thread2 = threadtester(2, "Second Thread", 2) thread3 = threadtester(3, "Third Thread", 3) thread1.start() thread2.start() thread3.start() thread1.join() thread2.join() thread3.join()
นี่จะเป็นผลลัพธ์เมื่อคุณรันโค้ดด้านบน:
คำอธิบายรหัส
- ส่วนนี้จะเหมือนกับตัวอย่างก่อนหน้าของเรา ที่นี่ คุณจะนำเข้าโมดูลเวลาและเธรดซึ่งใช้ในการจัดการการดำเนินการและความล่าช้าของ Python หัวข้อ
- ในบิตนี้ คุณกำลังสร้างคลาสที่เรียกว่า threadtester ซึ่งจะสืบทอดหรือขยาย ด้าย คลาสของโมดูลเธรด นี่เป็นหนึ่งในวิธีทั่วไปในการสร้างเธรดใน Python อย่างไรก็ตาม คุณควรแทนที่ตัวสร้างและตัวสร้างเท่านั้น วิ่ง() วิธีการในแอปของคุณ ดังที่คุณเห็นในตัวอย่างโค้ดข้างต้น __ในนั้น__ วิธีการ (ตัวสร้าง) ได้รับการแทนที่แล้ว ในทำนองเดียวกัน คุณได้แทนที่ด้วย วิ่ง() วิธี. ประกอบด้วยโค้ดที่คุณต้องการดำเนินการภายในเธรด ในตัวอย่างนี้ คุณได้เรียกใช้ฟังก์ชัน thread_test()
- นี่คือเมธอด thread_test() ซึ่งรับค่าของ i ในฐานะอาร์กิวเมนต์ ให้ลดลง 1 ในแต่ละการวนซ้ำและวนซ้ำโค้ดที่เหลือจนกระทั่ง i กลายเป็น 0 ในการวนซ้ำแต่ละครั้ง จะพิมพ์ชื่อของเธรดที่กำลังดำเนินการอยู่และพักการทำงานเพื่อรอวินาที (ซึ่งถือเป็นอาร์กิวเมนต์ด้วย ).
- thread1 = threadtester(1, “First Thread”, 1) ที่นี่ เรากำลังสร้างเธรดและส่งผ่านพารามิเตอร์ทั้งสามที่เราประกาศใน __init__ พารามิเตอร์แรกคือ id ของเธรด พารามิเตอร์ที่สองคือชื่อของเธรด และพารามิเตอร์ที่สามคือตัวนับ ซึ่งจะกำหนดจำนวนครั้งที่ลูป while ควรรัน
- thread2.start()เมธอด start ใช้เพื่อเริ่มการทำงานของเธรด ภายในฟังก์ชัน start() เรียกใช้เมธอด run() ของคลาสของคุณ
- thread3.join() วิธีการ join() บล็อกการทำงานของโค้ดอื่น ๆ และรอจนกว่าเธรดที่ถูกเรียกว่าเสร็จสิ้น
อย่างที่คุณทราบอยู่แล้วว่าเธรดที่อยู่ในกระบวนการเดียวกันจะสามารถเข้าถึงหน่วยความจำและข้อมูลของกระบวนการนั้นได้ ดังนั้น หากมีเธรดมากกว่าหนึ่งเธรดพยายามเปลี่ยนแปลงหรือเข้าถึงข้อมูลพร้อมกัน ข้อผิดพลาดอาจเกิดขึ้นได้
ในส่วนถัดไป คุณจะเห็นภาวะแทรกซ้อนประเภทต่างๆ ที่สามารถปรากฏขึ้นเมื่อเธรดเข้าถึงข้อมูลและส่วนสำคัญโดยไม่ต้องตรวจสอบธุรกรรมการเข้าถึงที่มีอยู่
เดดล็อคและสภาวะการแข่งขัน
ก่อนที่จะเรียนรู้เกี่ยวกับเดดล็อกและเงื่อนไขการแข่งขัน จะเป็นประโยชน์หากเข้าใจคำจำกัดความพื้นฐานบางประการที่เกี่ยวข้องกับการเขียนโปรแกรมพร้อมกัน:
- ส่วนที่สำคัญคือส่วนหนึ่งของโค้ดที่เข้าถึงหรือแก้ไขตัวแปรที่แชร์กันและจะต้องดำเนินการเป็นธุรกรรมแบบอะตอม
- การสลับบริบทเป็นกระบวนการที่ CPU ปฏิบัติตามเพื่อเก็บสถานะของเธรดก่อนที่จะเปลี่ยนจากงานหนึ่งไปยังอีกงานหนึ่ง เพื่อที่จะสามารถดำเนินการต่อจากจุดเดียวกันในภายหลังได้
การหยุดชะงัก
การหยุดชะงัก เป็นปัญหาที่นักพัฒนามักเผชิญเมื่อเขียนแอปพลิเคชันแบบพร้อมกัน/มัลติเธรดใน Python วิธีที่ดีที่สุดในการทำความเข้าใจปัญหาเดดล็อกคือการใช้ปัญหาตัวอย่างคลาสสิกของวิทยาการคอมพิวเตอร์ที่เรียกว่า ห้องอาหาร Philoปัญหาโซเฟอร์
คำชี้แจงปัญหาสำหรับนักปรัชญาการรับประทานอาหารมีดังนี้:
นักปรัชญาห้าคนนั่งอยู่บนโต๊ะกลมพร้อมจานสปาเก็ตตี้ (พาสต้าประเภทหนึ่ง) ห้าจาน และส้อมห้าคัน ดังที่แสดงในแผนภาพ
ในช่วงเวลาใดก็ตาม นักปรัชญาจะต้องกินหรือกำลังคิด
นอกจากนี้ นักปรัชญาต้องหยิบส้อมสองอันที่อยู่ติดกับตัวเขา (นั่นคือส้อมซ้ายและขวา) ก่อนจึงจะกินสปาเก็ตตี้ได้ ปัญหาทางตันเกิดขึ้นเมื่อนักปรัชญาทั้งห้าคนหยิบส้อมขวาพร้อมกัน
เนื่องจากนักปรัชญาแต่ละคนมีส้อมคนละหนึ่งอัน พวกเขาจึงต้องรอให้คนอื่นวางส้อมลง ผลก็คือไม่มีใครกินสปาเก็ตตี้ได้
ในทำนองเดียวกัน ในระบบที่ทำงานพร้อมกัน จะเกิดการหยุดชะงักเมื่อเธรดหรือกระบวนการต่างๆ (นักปรัชญา) พยายามจะรับทรัพยากรระบบที่ใช้ร่วมกัน (forks) ในเวลาเดียวกัน ส่งผลให้กระบวนการต่างๆ ไม่มีโอกาสดำเนินการ เนื่องจากกำลังรอทรัพยากรอื่นที่กระบวนการอื่นถืออยู่
เงื่อนไขการแข่งขัน
สภาวะการแข่งขันคือสถานะที่ไม่ต้องการของโปรแกรมซึ่งเกิดขึ้นเมื่อระบบดำเนินการสองอย่างหรือมากกว่านั้นพร้อมกัน ตัวอย่างเช่น ลองพิจารณาลูป for แบบง่ายๆ นี้:
i=0; # a global variable for x in range(100): print(i) i+=1;
ถ้าคุณสร้าง n จำนวนเธรดที่รันโค้ดนี้พร้อมกัน คุณไม่สามารถระบุค่าของ i (ซึ่งเธรดใช้ร่วมกัน) เมื่อโปรแกรมเสร็จสิ้นการดำเนินการ เนื่องจากในสภาพแวดล้อมแบบมัลติเธรดจริง เธรดสามารถทับซ้อนกันได้ และค่าของ i ที่ถูกดึงและแก้ไขโดยเธรดสามารถเปลี่ยนแปลงได้ในระหว่างที่เธรดอื่นเข้าถึงเธรดนั้น
นี่คือปัญหาหลักสองประเภทที่อาจเกิดขึ้นในแอปพลิเคชัน Python แบบมัลติเธรดหรือแบบกระจาย ในหัวข้อถัดไป คุณจะได้เรียนรู้วิธีเอาชนะปัญหานี้ด้วยการซิงโครไนซ์เธรด
Syncหัวข้อการขัดสี
เพื่อจัดการกับเงื่อนไขการแข่งขัน เดดล็อก และปัญหาอื่นๆ ที่เกี่ยวข้องกับเธรด โมดูลเธรดจะจัดเตรียม ล็อค วัตถุ แนวคิดคือเมื่อเธรดต้องการเข้าถึงทรัพยากรเฉพาะ เธรดนั้นจะต้องล็อกทรัพยากรนั้น เมื่อเธรดล็อกทรัพยากรเฉพาะแล้ว เธรดอื่นจะไม่สามารถเข้าถึงทรัพยากรนั้นได้จนกว่าจะปลดล็อก ดังนั้น การเปลี่ยนแปลงทรัพยากรจะเป็นแบบอะตอมมิก และจะหลีกเลี่ยงเงื่อนไขการแข่งขัน
ล็อคคือการซิงโครไนซ์ระดับต่ำแบบดั้งเดิมที่ใช้งานโดย __เกลียว โมดูล. ในเวลาใดก็ตาม การล็อกอาจอยู่ในสถานะใดสถานะหนึ่งจาก 2 สถานะ: ล็อค or ปลดล็อค รองรับสองวิธี:
- ได้รับ()เมื่อสถานะล็อคถูกปลดล็อค การเรียกวิธีการรับ() จะเปลี่ยนสถานะเป็นล็อคและส่งคืน อย่างไรก็ตาม หากสถานะถูกล็อค การเรียกเพื่อรับ() จะถูกบล็อกจนกว่าเมธอด release() จะถูกเรียกโดยเธรดอื่น
- ปล่อย()release() ใช้เพื่อตั้งค่าสถานะให้ปลดล็อค เช่น ปลดล็อค สามารถเรียกได้โดยเธรดใดๆ ไม่จำเป็นต้องเป็นเธรดที่ได้รับการล็อค
นี่คือตัวอย่างการใช้การล็อคในแอพของคุณ ยิงขึ้นของคุณ IDLE และพิมพ์ดังต่อไปนี้:
import threading lock = threading.Lock() def first_function(): for i in range(5): lock.acquire() print ('lock acquired') print ('Executing the first funcion') lock.release() def second_function(): for i in range(5): lock.acquire() print ('lock acquired') print ('Executing the second funcion') lock.release() if __name__=="__main__": thread_one = threading.Thread(target=first_function) thread_two = threading.Thread(target=second_function) thread_one.start() thread_two.start() thread_one.join() thread_two.join()
ตอนนี้กด F5 คุณควรเห็นผลลัพธ์ดังนี้:
คำอธิบายรหัส
- ที่นี่ คุณเพียงแค่สร้างล็อคใหม่โดยการโทรไปที่ เกลียวล็อค() ฟังก์ชั่นโรงงาน ภายใน Lock() ส่งคืนอินสแตนซ์ของคลาส Lock ที่เป็นรูปธรรมที่มีประสิทธิภาพสูงสุดซึ่งดูแลโดยแพลตฟอร์ม
- ในคำสั่งแรก คุณจะได้รับล็อคโดยการเรียกเมธอด Acquis() เมื่ออนุญาตให้ล็อคแล้ว คุณจะพิมพ์ “ได้รับล็อคแล้ว” ไปที่คอนโซล เมื่อโค้ดทั้งหมดที่คุณต้องการให้เธรดทำงานเสร็จสิ้นแล้ว คุณสามารถปลดล็อคโดยการเรียกเมธอด release()
ทฤษฎีนี้ใช้ได้ แต่คุณจะรู้ได้อย่างไรว่าล็อกนั้นใช้งานได้จริง หากคุณดูผลลัพธ์ คุณจะเห็นว่าคำสั่งพิมพ์แต่ละคำสั่งจะพิมพ์ทีละบรรทัดพอดี จำไว้ว่าในตัวอย่างก่อนหน้านี้ ผลลัพธ์จากคำสั่งพิมพ์จะพิมพ์แบบไม่สม่ำเสมอเนื่องจากมีเธรดหลายเธรดเข้าถึงเมธอด print() ในเวลาเดียวกัน ในที่นี้ ฟังก์ชันพิมพ์จะถูกเรียกใช้หลังจากล็อกแล้วเท่านั้น ดังนั้น ผลลัพธ์จะแสดงทีละรายการและทีละบรรทัด
นอกเหนือจากการล็อคแล้ว Python ยังรองรับกลไกอื่น ๆ ในการจัดการการซิงโครไนซ์เธรด ดังต่อไปนี้:
- อาร์ล็อคส์
- Semaphores
- เงื่อนไข
- เหตุการณ์และ
- ปัญหาและอุปสรรคที่
Global Interpreter Lock (และวิธีจัดการกับมัน)
ก่อนที่จะเจาะลึกรายละเอียดของ GIL ของ Python เรามาทำความเข้าใจคำศัพท์บางคำที่เป็นประโยชน์ในการทำความเข้าใจหัวข้อถัดไปกันก่อน:
- รหัสที่เชื่อมโยงกับ CPU: หมายถึงส่วนใด ๆ ของโค้ดที่ CPU จะดำเนินการโดยตรง
- รหัส I/O-bound: นี่อาจเป็นรหัสใดก็ได้ที่เข้าถึงระบบไฟล์ผ่านระบบปฏิบัติการ
- CPython: มันเป็นข้อมูลอ้างอิง การดำเนินงาน of Python และสามารถอธิบายได้ว่าเป็นล่ามที่เขียนด้วยภาษาซีและ Python (ภาษาโปรแกรม).
GIL คืออะไร Python?
ล็อคล่ามสากล (GIL) ใน python เป็นการล็อคกระบวนการหรือ mutex ที่ใช้ในขณะที่จัดการกับกระบวนการ ทำให้แน่ใจว่าเธรดหนึ่งสามารถเข้าถึงทรัพยากรเฉพาะในแต่ละครั้ง และยังป้องกันการใช้วัตถุและรหัสไบต์ในครั้งเดียว สิ่งนี้จะเป็นประโยชน์ต่อโปรแกรมแบบเธรดเดียวในการเพิ่มประสิทธิภาพ GIL ใน python นั้นเรียบง่ายและนำไปใช้งานได้ง่ายมาก
สามารถใช้การล็อกเพื่อให้แน่ใจว่ามีเธรดเดียวเท่านั้นที่สามารถเข้าถึงทรัพยากรเฉพาะในเวลาที่กำหนด
คุณสมบัติอย่างหนึ่งของ Python คือการใช้การล็อคทั่วโลกกับกระบวนการอินเทอร์พรีเตอร์แต่ละกระบวนการ ซึ่งหมายความว่ากระบวนการแต่ละกระบวนการจะปฏิบัติต่ออินเทอร์พรีเตอร์ Python เองเป็นทรัพยากร
ตัวอย่างเช่น สมมติว่าคุณเขียนโปรแกรม Python ที่ใช้เธรดสองเธรดในการดำเนินการทั้ง CPU และ 'I/O' เมื่อคุณเรียกใช้โปรแกรมนี้ สิ่งที่เกิดขึ้นคือ:
- ล่ามหลามสร้างกระบวนการใหม่และวางไข่เธรด
- เมื่อเธรด-1 เริ่มทำงาน มันจะรับ GIL และล็อคมันก่อน
- หากเธรด-2 ต้องการดำเนินการตอนนี้ จะต้องรอให้ GIL เปิดตัว แม้ว่าโปรเซสเซอร์อื่นจะว่างก็ตาม
- สมมติว่าเธรด 1 กำลังรอการดำเนินการ I/O ในขณะนี้ เธรด 2 จะปล่อย GIL และเธรด XNUMX จะเข้าควบคุม GIL
- หลังจากดำเนินการ I/O เสร็จสิ้นแล้ว หาก thread-1 ต้องการดำเนินการตอนนี้ จะต้องรอให้ GIL ถูกรีลีสโดย thread-2 อีกครั้ง
ด้วยเหตุนี้ มีเพียงเธรดเดียวเท่านั้นที่สามารถเข้าถึงล่ามได้ตลอดเวลา ซึ่งหมายความว่าจะมีเธรดเดียวเท่านั้นที่รันโค้ดหลาม ณ เวลาที่กำหนด
ซึ่งเป็นเรื่องปกติสำหรับโปรเซสเซอร์แบบ single-core เนื่องจากจะต้องใช้การแบ่งเวลา (ดูส่วนแรกของบทช่วยสอนนี้) เพื่อจัดการกับเธรด อย่างไรก็ตาม ในกรณีของโปรเซสเซอร์แบบมัลติคอร์ ฟังก์ชันที่เชื่อมโยงกับ CPU ที่ทำงานบนหลายเธรดจะมีผลกระทบอย่างมากต่อประสิทธิภาพของโปรแกรม เนื่องจากไม่ได้ใช้งานคอร์ที่มีอยู่ทั้งหมดในเวลาเดียวกัน
เหตุใด GIL จึงจำเป็น?
คPython ตัวรวบรวมขยะใช้เทคนิคการจัดการหน่วยความจำที่มีประสิทธิภาพที่เรียกว่าการนับการอ้างอิง วิธีการทำงานมีดังนี้: ออบเจ็กต์ทุกตัวใน Python จะมีจำนวนการอ้างอิง ซึ่งจะเพิ่มขึ้นเมื่อกำหนดให้กับตัวแปรใหม่หรือเพิ่มลงในคอนเทนเนอร์ (เช่น ทูเพิล รายการ เป็นต้น) ในทำนองเดียวกัน จำนวนการอ้างอิงจะลดลงเมื่อการอ้างอิงอยู่นอกขอบเขตหรือเมื่อเรียกใช้คำสั่ง del เมื่อจำนวนการอ้างอิงของออบเจ็กต์ถึง 0 ออบเจ็กต์นั้นจะถูกรวบรวมขยะและหน่วยความจำที่จัดสรรไว้จะได้รับการปลดปล่อย
แต่ปัญหาคือตัวแปรจำนวนการอ้างอิงนั้นมีแนวโน้มที่จะเกิดสภาวะการแข่งขันเช่นเดียวกับตัวแปรทั่วโลกอื่นๆ เพื่อแก้ปัญหานี้ นักพัฒนา Python จึงตัดสินใจใช้การล็อกอินเทอร์พรีเตอร์ทั่วโลก อีกทางเลือกหนึ่งคือการเพิ่มการล็อกให้กับแต่ละอ็อบเจ็กต์ซึ่งจะส่งผลให้เกิดเดดล็อกและเพิ่มค่าใช้จ่ายจากการเรียกใช้ acquire() และ release()
ดังนั้น GIL จึงเป็นข้อจำกัดที่สำคัญสำหรับโปรแกรม Python แบบมัลติเธรดที่รันการทำงานที่จำกัดด้วย CPU จำนวนมาก (ทำให้ทำงานแบบเธรดเดียวได้อย่างมีประสิทธิภาพ) หากคุณต้องการใช้คอร์ CPU หลายคอร์ในแอปพลิเคชันของคุณ ให้ใช้ มัลติโปรเซสเซอร์ โมดูลแทน
สรุป
- Python รองรับ 2 โมดูลสำหรับมัลติเธรด:
- __เกลียว โมดูล: มีการใช้งานระดับต่ำสำหรับเธรดและล้าสมัย
- โมดูลเกลียว: ให้การใช้งานระดับสูงสำหรับมัลติเธรดและเป็นมาตรฐานปัจจุบัน
- ในการสร้างเธรดโดยใช้โมดูลเธรด คุณต้องดำเนินการดังต่อไปนี้:
- สร้างคลาสที่ขยาย ด้าย ชั้นเรียน
- แทนที่ตัวสร้างของมัน (__init__)
- แทนที่มัน วิ่ง() วิธี
- สร้างวัตถุของคลาสนี้
- เธรดสามารถดำเนินการได้โดยการเรียก เริ่ม () วิธี
- เทศกาล เข้าร่วม () สามารถใช้เมธอดนี้เพื่อบล็อกเธรดอื่นได้จนกว่าเธรดนี้ (เธรดที่ถูกเรียกใช้การรวม) จะดำเนินการเสร็จสิ้น
- สภาวะการแข่งขันเกิดขึ้นเมื่อหลายเธรดเข้าถึงหรือแก้ไขทรัพยากรที่ใช้ร่วมกันในเวลาเดียวกัน
- สามารถหลีกเลี่ยงได้โดย Syncหัวข้อการขัดสี
- Python รองรับ 6 วิธีในการซิงโครไนซ์เธรด:
- ล็อค
- อาร์ล็อคส์
- Semaphores
- เงื่อนไข
- เหตุการณ์และ
- ปัญหาและอุปสรรคที่
- ล็อคอนุญาตให้เฉพาะเธรดเฉพาะที่ได้รับล็อคเพื่อเข้าสู่ส่วนที่สำคัญ
- การล็อคมี 2 วิธีหลัก:
- ได้รับ(): ตั้งค่าสถานะล็อคเป็น ล็อค หากเรียกใช้บนวัตถุที่ถูกล็อค มันจะบล็อกจนกว่าทรัพยากรจะว่าง
- ปล่อย(): ตั้งค่าสถานะล็อคเป็น ปลดล็อค และกลับมา หากเรียกใช้บนวัตถุที่ปลดล็อคจะส่งคืนค่าเท็จ
- การล็อคล่ามส่วนกลางเป็นกลไกที่มีเพียง 1 C เท่านั้นPython กระบวนการล่ามสามารถดำเนินการได้ในแต่ละครั้ง
- มันถูกใช้เพื่ออำนวยความสะดวกให้กับฟังก์ชันการนับการอ้างอิงของ CPythonคนเก็บขยะของเอส
- ที่จะทำให้ Python แอพที่มีการใช้งาน CPU หนักๆ ควรใช้โมดูลการประมวลผลหลายตัว