อ่าน 8 นาที
ล็อค (วิทยาการคอมพิวเตอร์)
ในวิทยาการคอมพิวเตอร์ล็อกหรือมิวเท็กซ์ (มาจากหลักการกีดกันร่วมกัน ) คือกลไกการซิงโครไนซ์ ที่ป้องกันไม่ให้เธรดการ ทำงานหลายเธรดแก้ไขหรือเข้าถึงสถานะพร้อมกัน ล็อกบังคับใช้หลักการ
ล็อค (วิทยาการคอมพิวเตอร์)
ในวิทยาการคอมพิวเตอร์ล็อกหรือมิวเท็กซ์ (มาจากหลักการกีดกันร่วมกัน ) คือกลไกการซิงโครไนซ์ ที่ป้องกันไม่ให้เธรดการ ทำงานหลายเธรดแก้ไขหรือเข้าถึงสถานะพร้อมกัน ล็อกบังคับใช้หลักการ ควบคุมการทำงานพร้อมกันแบบกีดกันร่วมกันและด้วยวิธีการที่หลากหลาย ทำให้มีการใช้งานที่แตกต่างกันไปในแต่ละแอปพลิเคชัน
ประเภท
โดยทั่วไปแล้ว ล็อกจะเป็นล็อกแบบให้คำแนะนำซึ่งแต่ละเธรดจะให้ความร่วมมือโดยการขอรับล็อกก่อนที่จะเข้าถึงข้อมูลที่เกี่ยวข้อง บางระบบยังใช้ล็อกแบบบังคับซึ่งการพยายามเข้าถึงทรัพยากรที่ถูกล็อกโดยไม่ได้รับอนุญาตจะทำให้เกิดข้อผิดพลาดในเอนทิตีที่พยายามเข้าถึงนั้น
ระบบล็อกที่ง่ายที่สุดคือเซมาฟอร์ แบบไบนารี ซึ่งให้สิทธิ์การเข้าถึงข้อมูลที่ถูกล็อกแต่เพียงผู้เดียว นอกจากนี้ยังมีระบบล็อกแบบอื่นที่ให้สิทธิ์การเข้าถึงแบบแบ่งปันสำหรับการอ่านข้อมูล โหมดการเข้าถึงอื่นๆ ที่ใช้กันอย่างแพร่หลาย ได้แก่ การเข้าถึงแต่เพียงผู้เดียว การเข้าถึงโดยตั้งใจที่จะยกเว้น และการเข้าถึงโดยตั้งใจที่จะอัปเกรด
อีกวิธีหนึ่งในการจำแนกประเภทของล็อกคือการพิจารณาสิ่งที่เกิดขึ้นเมื่อกลยุทธ์การล็อกขัดขวางการทำงานของเธรด การออกแบบล็อกส่วนใหญ่จะบล็อกการทำงานของเธรดที่ร้องขอการล็อกจนกว่าจะได้รับอนุญาตให้เข้าถึงทรัพยากรที่ถูกล็อก สำหรับสปินล็อกเธรดจะรอ ("หมุน") จนกว่าล็อกจะพร้อมใช้งาน วิธีนี้มีประสิทธิภาพหากเธรดถูกบล็อกเป็นเวลาสั้นๆ เพราะจะหลีกเลี่ยงภาระงานของระบบปฏิบัติการในการจัดตารางการทำงานของกระบวนการใหม่ แต่จะไม่มีประสิทธิภาพหากล็อกถูกถือครองเป็นเวลานาน หรือหากความคืบหน้าของเธรดที่ถือครองล็อกขึ้นอยู่กับการแย่งชิงการทำงานของเธรดที่ถูกล็อก
โดยทั่วไปแล้ว การล็อกต้องอาศัยการสนับสนุนจากฮาร์ดแวร์เพื่อการใช้งานที่มีประสิทธิภาพ การสนับสนุนนี้มักอยู่ในรูปแบบของ คำสั่ง อะตอมิก อย่างน้อยหนึ่ง คำสั่ง เช่น " test-and-set ", " fetch-and-add " หรือ " compare-and-swap " คำสั่งเหล่านี้ช่วยให้กระบวนการเดียวสามารถทดสอบได้ว่าล็อกว่างหรือไม่ และหากว่าง ก็สามารถล็อกได้ในขั้นตอนอะตอมิกเดียว
สถาปัตยกรรม โปรเซสเซอร์เดี่ยวมีตัวเลือกในการใช้ลำดับคำสั่งที่ไม่สามารถขัดจังหวะได้—โดยใช้คำสั่งพิเศษหรือคำนำหน้าคำสั่งเพื่อปิดใช้งานการขัดจังหวะชั่วคราว—แต่เทคนิคนี้ใช้ไม่ได้กับ เครื่อง มัลติโปรเซสเซอร์ ที่มีหน่วยความจำร่วมกัน การรองรับการล็อกอย่างเหมาะสมในสภาพแวดล้อมมัลติโปรเซสเซอร์อาจต้องใช้ฮาร์ดแวร์หรือซอฟต์แวร์ที่ซับซ้อนมาก พร้อมกับ ปัญหา การซิงโครไนซ์ที่สำคัญ
เหตุผล ที่ต้องใช้ การดำเนินการแบบอะตอมิกก็คือเรื่องการทำงานพร้อมกัน (concurrency) ซึ่งหมายถึงมีงานมากกว่าหนึ่งงานที่ทำงานตรรกะเดียวกัน ตัวอย่างเช่น พิจารณา โค้ด ภาษา C ต่อไปนี้ :
ถ้า( lock == 0 ) { // ถ้า lock ว่าง ให้ตั้งค่า lock = pid ; }ตัวอย่างข้างต้นไม่ได้รับประกันว่างานนั้นจะมีล็อกอยู่ เนื่องจากอาจมีงานมากกว่าหนึ่งงานที่กำลังทดสอบล็อกในเวลาเดียวกัน เนื่องจากทั้งสองงานจะตรวจพบว่าล็อกว่างอยู่ ทั้งสองงานจึงจะพยายามตั้งค่าล็อกโดยไม่รู้ว่าอีกงานหนึ่งก็กำลังตั้งค่าล็อกอยู่เช่น กัน อัลกอริทึม ของ DekkerหรือPetersonสามารถใช้แทนได้หากการดำเนินการล็อกแบบอะตอมิกไม่สามารถใช้งานได้
การใช้งานล็อกอย่างไม่ระมัดระวังอาจส่งผลให้เกิดภาวะติดตายหรือภาวะติดขัดได้ มีกลยุทธ์หลายอย่างที่สามารถใช้เพื่อหลีกเลี่ยงหรือแก้ไขภาวะติดตายหรือภาวะติดขัดได้ ทั้งในขั้นตอนการออกแบบและการทำงาน (กลยุทธ์ที่พบได้บ่อยที่สุดคือการกำหนดลำดับการเข้าถึงล็อกให้เป็นมาตรฐาน เพื่อให้ล็อกที่มีความสัมพันธ์กันถูกเข้าถึงในลำดับ "แบบเรียงลำดับ" ที่กำหนดไว้อย่างชัดเจนเสมอ)
บางภาษาโปรแกรมรองรับการล็อกด้วยไวยากรณ์ ตัวอย่างในภาษา C#มีดังต่อไปนี้:
เนมสเปซWikipedia.Examples ;การใช้System.Threading ;public class Account // นี่คือตัวตรวจสอบบัญชี{ // ใช้ `object` ในเวอร์ชันก่อน C# 13 private readonly Lock _balanceLock = new (); private decimal _balance = 0 ;public void Deposit ( decimal amount ) { // อนุญาตให้เธรดเพียงเธรดเดียวเท่านั้นที่สามารถเรียกใช้คำสั่งนี้ได้ในแต่ละครั้งlock ( _balanceLock ) { _balance += amount ; } }public void Withdraw ( decimal amount ) { // อนุญาตให้เธรดเพียงเธรดเดียวเท่านั้นที่สามารถเรียกใช้คำสั่งนี้ได้ในแต่ละครั้งlock ( _balanceLock ) { _balance -= amount ; } } }C# ได้นำSystem.Threading.Lock มาใช้ ใน C# 13 บน.NET 9
รหัสนี้lock(this)อาจก่อให้เกิดปัญหาได้หากสามารถเข้าถึงอินสแตนซ์ได้จากภายนอก[ 1 ]
เช่นเดียวกับJavaภาษา C# ก็สามารถซิงโครไนซ์เมธอดทั้งหมดได้โดยใช้แอตทริบิวต์ MethodImplOptions.Synchronized [ 2 ] [ 3 ]
[MethodImpl(MethodImplOptions.Synchronized)] public void SomeMethod () { // ทำบางอย่าง}ความละเอียด
ก่อนที่จะทำความเข้าใจเกี่ยวกับระดับความละเอียดของการล็อก (lock granularity) จำเป็นต้องเข้าใจแนวคิดพื้นฐานสามประการเกี่ยวกับการล็อกก่อน:
- ค่าใช้จ่ายเพิ่มเติมในการใช้ล็อก : ทรัพยากรพิเศษที่ใช้ไปกับการใช้ล็อก เช่น พื้นที่หน่วยความจำที่จัดสรรให้กับล็อก เวลาของ CPU ในการเริ่มต้นและทำลายล็อก และเวลาในการได้มาหรือปล่อยล็อก ยิ่งโปรแกรมใช้ล็อกมากเท่าไร ค่าใช้จ่ายเพิ่มเติมที่เกี่ยวข้องกับการใช้งานก็จะยิ่งมากขึ้นเท่านั้น
- การแย่งชิงล็อก : ปัญหานี้เกิดขึ้นเมื่อกระบวนการหรือเธรดหนึ่งพยายามขอรับล็อกที่กระบวนการหรือเธรดอื่นถือครองอยู่ ยิ่งล็อกที่มีอยู่มีความละเอียดมากเท่าไหร่ โอกาสที่กระบวนการ/เธรดหนึ่งจะร้องขอล็อกที่อีกกระบวนการ/เธรดหนึ่งถือครองอยู่ก็จะยิ่งน้อยลงเท่านั้น (ตัวอย่างเช่น การล็อกแถวแทนที่จะล็อกทั้งตาราง หรือการล็อกเซลล์แทนที่จะล็อกทั้งแถว)
- เดดล็อก (Deadlock) : สถานการณ์ที่งานอย่างน้อยสองงานต่างรอการล็อกที่งานอีกงานหนึ่งถือครองอยู่ หากไม่มีการดำเนินการใดๆ งานทั้งสองจะรอไปตลอดกาล
ในการเลือกจำนวนล็อกสำหรับการซิงโครไนซ์นั้น จำเป็นต้องมีการแลกเปลี่ยนระหว่างการลดภาระการทำงานของล็อกและการลดการแย่งชิงล็อก
คุณสมบัติสำคัญอย่างหนึ่งของล็อกคือระดับความละเอียด (granularity ) ระดับความละเอียดนี้เป็นตัววัดปริมาณข้อมูลที่ล็อกนั้นปกป้อง โดยทั่วไป การเลือกความละเอียดที่หยาบ (จำนวนล็อกน้อย แต่ละล็อกปกป้องข้อมูลส่วนใหญ่) จะส่งผลให้ค่าใช้จ่ายในการล็อก น้อยลง เมื่อกระบวนการเดียวเข้าถึงข้อมูลที่ได้รับการปกป้อง แต่ประสิทธิภาพจะแย่ลงเมื่อหลายกระบวนการทำงานพร้อมกัน เนื่องจากมีการแย่งชิงล็อก เพิ่มขึ้น ยิ่งล็อกหยาบมากเท่าไหร่ โอกาสที่ล็อกจะหยุดกระบวนการที่ไม่เกี่ยวข้องไม่ให้ดำเนินการต่อก็ยิ่งสูงขึ้นเท่านั้น ในทางกลับกัน การใช้ความละเอียดที่ละเอียด (จำนวนล็อกมากขึ้น แต่ละล็อกปกป้องข้อมูลจำนวนเล็กน้อย) จะเพิ่มค่าใช้จ่ายในการล็อกเอง แต่จะลดการแย่งชิงล็อก การล็อกแบบละเอียดที่แต่ละกระบวนการต้องถือล็อกหลายตัวจากชุดล็อกทั่วไปอาจสร้างความสัมพันธ์แบบพึ่งพาของล็อกที่ซับซ้อน ความซับซ้อนนี้อาจเพิ่มโอกาสที่โปรแกรมเมอร์จะสร้างภาวะการติดตาย (deadlock ) โดยไม่รู้ตัว
ในระบบจัดการฐานข้อมูลตัวอย่างเช่น การล็อกสามารถป้องกันส่วนต่างๆ ได้ โดยเรียงลำดับจากระดับความละเอียดต่ำไปสูง ได้แก่ ส่วนหนึ่งของฟิลด์ ฟิลด์ทั้งหมด เรคอร์ด หน้าข้อมูล หรือตารางทั้งหมด การล็อกระดับหยาบ เช่น การล็อกตาราง มักให้ประสิทธิภาพที่ดีที่สุดสำหรับผู้ใช้คนเดียว ในขณะที่การล็อกระดับละเอียด เช่น การล็อกเรคอร์ด มักให้ประสิทธิภาพที่ดีที่สุดสำหรับผู้ใช้หลายคน
การล็อกฐานข้อมูล
การล็อกฐานข้อมูลสามารถใช้เป็นวิธีการเพื่อให้มั่นใจถึงความสอดคล้องกันของธุรกรรม กล่าวคือ เมื่อทำการประมวลผลธุรกรรมพร้อมกัน (การสลับธุรกรรม) การใช้การล็อกแบบ 2 ขั้นตอนจะช่วยให้การดำเนินการพร้อมกันของธุรกรรมนั้นเทียบเท่ากับการดำเนินการตามลำดับแบบอนุกรม อย่างไรก็ตาม การติดตาย (deadlock) กลายเป็นผลข้างเคียงที่ไม่พึงประสงค์ของการล็อกในฐานข้อมูล การติดตายสามารถป้องกันได้โดยการกำหนดลำดับการล็อกระหว่างธุรกรรมล่วงหน้า หรือตรวจจับได้โดยใช้กราฟการรอคอย ( waits-for graphs ) ทางเลือกอื่นนอกเหนือจากการล็อกเพื่อให้ฐานข้อมูลมีความสอดคล้องกันในขณะที่หลีกเลี่ยงการติดตาย คือการใช้การประทับเวลาทั่วโลกที่มีลำดับสมบูรณ์ (totally ordered global timestamps)
มีกลไกที่ใช้ในการจัดการการกระทำของผู้ใช้หลายคนพร้อมกันบนฐานข้อมูล โดยมีจุดประสงค์เพื่อป้องกันการอัปเดตที่สูญหายและการอ่านข้อมูลที่ไม่ถูกต้อง การล็อกมีสองประเภท ได้แก่การล็อกแบบมองโลกในแง่ร้ายและการล็อกแบบมองโลกในแง่ดี :
- การล็อกแบบมองโลกในแง่ร้าย : ผู้ใช้ที่อ่านบันทึกโดยมีเจตนาที่จะแก้ไขข้อมูล จะล็อกบันทึกนั้นไว้แต่เพียงผู้เดียวเพื่อป้องกันไม่ให้ผู้ใช้รายอื่นแก้ไขได้ ซึ่งหมายความว่าไม่มีใครสามารถแก้ไขบันทึกนั้นได้จนกว่าผู้ใช้จะปลดล็อก ข้อเสียคือ ผู้ใช้อาจถูกล็อกไม่ให้เข้าถึงข้อมูลเป็นเวลานานมาก ทำให้ระบบตอบสนองช้าลงและก่อให้เกิดความหงุดหงิด
- ควรใช้การล็อกแบบมองโลกในแง่ร้ายในกรณีใดบ้าง: วิธีนี้ส่วนใหญ่ใช้ในสภาพแวดล้อมที่มีการแย่งชิงข้อมูล (ปริมาณการร้องขอจากผู้ใช้ไปยังระบบฐานข้อมูลในเวลาเดียวกัน) สูง โดยที่ต้นทุนในการปกป้องข้อมูลผ่านการล็อกนั้นน้อยกว่าต้นทุนในการยกเลิกธุรกรรมหากเกิดข้อขัดแย้งในการทำงานพร้อมกัน การล็อกแบบมองโลกในแง่ร้ายจะเหมาะสมที่สุดเมื่อเวลาในการล็อกสั้น เช่น ในการประมวลผลข้อมูลด้วยโปรแกรม การล็อกแบบมองโลกในแง่ร้ายต้องการการเชื่อมต่อกับฐานข้อมูลอย่างต่อเนื่องและไม่ใช่ตัวเลือกที่ปรับขนาดได้เมื่อผู้ใช้โต้ตอบกับข้อมูล เนื่องจากข้อมูลอาจถูกล็อกเป็นเวลานานพอสมควร จึงไม่เหมาะสมสำหรับการใช้งานในการพัฒนาเว็บแอปพลิเคชัน
- การล็อกแบบมอง โลกในแง่ดี (Optimistic locking ): วิธีนี้อนุญาตให้ผู้ใช้หลายคนเข้าถึงฐานข้อมูลพร้อมกันได้ ในขณะที่ระบบจะเก็บสำเนาของการอ่านครั้งแรกที่ผู้ใช้แต่ละคนทำไว้ เมื่อผู้ใช้ต้องการอัปเดตเรคอร์ด แอปพลิเคชันจะตรวจสอบว่ามีผู้ใช้รายอื่นเปลี่ยนแปลงเรคอร์ดนั้นหรือไม่นับตั้งแต่การอ่านครั้งล่าสุด แอปพลิเคชันจะทำเช่นนั้นโดยการเปรียบเทียบการอ่านครั้งแรกที่เก็บไว้ในหน่วยความจำกับเรคอร์ดในฐานข้อมูลเพื่อตรวจสอบการเปลี่ยนแปลงใดๆ ที่เกิดขึ้นกับเรคอร์ด หากพบความไม่ตรงกันระหว่างการอ่านครั้งแรกกับเรคอร์ดในฐานข้อมูล จะถือเป็นการละเมิดกฎการทำงานพร้อมกัน และทำให้ระบบไม่สนใจคำขออัปเดตใดๆ ระบบจะสร้างข้อความแสดงข้อผิดพลาดและขอให้ผู้ใช้เริ่มกระบวนการอัปเดตใหม่อีกครั้ง วิธีนี้ช่วยปรับปรุงประสิทธิภาพของฐานข้อมูลโดยลดปริมาณการล็อกที่จำเป็นลง ซึ่งจะช่วยลดภาระบนเซิร์ฟเวอร์ฐานข้อมูล วิธีนี้ทำงานได้อย่างมีประสิทธิภาพกับตารางที่ต้องการการอัปเดตจำนวนจำกัด เนื่องจากไม่มีผู้ใช้คนใดถูกล็อก อย่างไรก็ตาม การอัปเดตบางอย่างอาจล้มเหลว ข้อเสียคือการอัปเดตล้มเหลวอย่างต่อเนื่องเนื่องจากปริมาณคำขออัปเดตจำนวนมากจากผู้ใช้หลายคนพร้อมกัน ซึ่งอาจทำให้ผู้ใช้รู้สึกหงุดหงิด
- ควรใช้การล็อกแบบมองโลกในแง่ดีที่ใด: วิธีนี้เหมาะสมในสภาพแวดล้อมที่มีการแย่งชิงข้อมูลต่ำ หรือเมื่อจำเป็นต้องเข้าถึงข้อมูลแบบอ่านอย่างเดียว การควบคุมการทำงานพร้อมกันแบบมองโลกในแง่ดีถูกนำมาใช้อย่างกว้างขวางใน .NET เพื่อตอบสนองความต้องการของแอปพลิเคชันบนมือถือและแอปพลิเคชันที่ไม่ได้เชื่อมต่อ[ 4 ]ซึ่งการล็อกแถวข้อมูลเป็นเวลานานๆ นั้นเป็นไปไม่ได้ นอกจากนี้ การรักษาการล็อกเรคอร์ดต้องอาศัยการเชื่อมต่ออย่างต่อเนื่องกับเซิร์ฟเวอร์ฐานข้อมูล ซึ่งเป็นไปไม่ได้ในแอปพลิเคชันที่ไม่ได้เชื่อมต่อ
ตารางความเข้ากันได้ของตัวล็อค
มีรูปแบบและการปรับปรุงเพิ่มเติมของประเภทล็อกหลักเหล่านี้อยู่หลายแบบ โดยแต่ละแบบมีพฤติกรรมการปิดกั้นที่แตกต่างกัน หากล็อกแรกปิดกั้นล็อกอื่น ล็อกทั้งสองจะเรียกว่าไม่เข้ากันมิฉะนั้นล็อกจะเรียกว่าเข้ากันได้บ่อยครั้งที่ในเอกสารทางเทคนิคจะแสดงปฏิสัมพันธ์ระหว่างประเภทล็อกที่ปิดกั้นโดยใช้ตารางความเข้ากันได้ของล็อกต่อไปนี้เป็นตัวอย่างของประเภทล็อกหลักที่ใช้กันทั่วไป:
| ประเภทล็อค | อ่าน-ล็อก | เขียนล็อก |
|---|---|---|
| อ่าน-ล็อก | ✔ | X |
| เขียนล็อก | X | X |
- ✔แสดงถึงความเข้ากันได้
- Xแสดงถึงความไม่เข้ากัน กล่าวคือ กรณีที่การล็อกประเภทแรก (ในคอลัมน์ซ้าย) บนวัตถุหนึ่งขัดขวางการล็อกประเภทที่สอง (ในแถวบน) ไม่ให้ถูกล็อกบนวัตถุเดียวกัน (โดยธุรกรรมอื่น) โดยทั่วไป วัตถุจะมีคิวของการดำเนินการที่ร้องขอ (โดยธุรกรรม) ที่รออยู่พร้อมการล็อกที่เกี่ยวข้อง การล็อกที่ถูกบล็อกสำหรับการดำเนินการแรกในคิวจะถูกล็อกทันทีที่การล็อกที่บล็อกอยู่ถูกลบออกจากวัตถุ และจากนั้นการดำเนินการที่เกี่ยวข้องจะถูกดำเนินการ หากการล็อกสำหรับการดำเนินการในคิวไม่ถูกบล็อกโดยการล็อกที่มีอยู่ (สามารถมีการล็อกที่เข้ากันได้หลายรายการบนวัตถุเดียวกันพร้อมกันได้) จะถูกล็อกทันที
หมายเหตุ:ในสิ่งพิมพ์บางฉบับ รายการในตารางจะถูกทำเครื่องหมายไว้ว่า "เข้ากันได้" หรือ "ไม่เข้ากัน" หรือ "ใช่" หรือ "ไม่ใช่" ตามลำดับ[ 5 ]
ข้อเสีย
การป้องกันทรัพยากรโดยใช้การล็อกและการซิงโครไนซ์เธรด/กระบวนการมีข้อเสียหลายประการ:
- ปัญหาการแย่งชิงทรัพยากร: บางเธรด/กระบวนการต้องรอจนกว่าล็อก (หรือชุดล็อกทั้งหมด) จะถูกปล่อย หากเธรดใดเธรดหนึ่งที่ถือล็อกอยู่เกิดหยุดทำงาน หยุดชะงัก บล็อก หรือเข้าสู่ลูปไม่สิ้นสุด เธรดอื่นๆ ที่รอล็อกอาจต้องรออย่างไม่มีกำหนดจนกว่าจะปิดและเปิด เครื่องคอมพิวเตอร์ ใหม่
- ค่าใช้จ่ายเพิ่มเติม: การใช้ล็อกจะเพิ่มค่าใช้จ่ายเพิ่มเติมสำหรับการเข้าถึงทรัพยากรแต่ละครั้ง แม้ว่าโอกาสที่จะเกิดการชนกันจะมีน้อยมากก็ตาม (อย่างไรก็ตาม โอกาสที่จะเกิดการชนกันใดๆ ก็ตามถือเป็นสภาวะการแข่งขัน )
- การแก้ไขข้อผิดพลาด: ข้อผิดพลาดที่เกี่ยวข้องกับการล็อกนั้นขึ้นอยู่กับเวลา และอาจมีความละเอียดอ่อนมากและยากต่อการจำลองอย่างยิ่ง เช่นการล็อกตาย (deadlock )
- ความไม่เสถียร: ความสมดุลที่เหมาะสมระหว่างค่าใช้จ่ายในการล็อกและการแย่งชิงการล็อกอาจมีความเฉพาะเจาะจงสำหรับโดเมนปัญหา (แอปพลิเคชัน) และมีความอ่อนไหวต่อการออกแบบ การใช้งาน และแม้แต่การเปลี่ยนแปลงสถาปัตยกรรมระบบระดับต่ำ ความสมดุลเหล่านี้อาจเปลี่ยนแปลงไปตลอดวงจรชีวิตของแอปพลิเคชัน และอาจต้องมีการเปลี่ยนแปลงครั้งใหญ่เพื่อปรับปรุง (ปรับสมดุลใหม่)
- ความสามารถในการประกอบเข้าด้วยกัน: การล็อกจะสามารถประกอบเข้าด้วยกันได้ (เช่น การจัดการการล็อกหลายรายการพร้อมกันเพื่อลบรายการ X จากตาราง A และแทรก X ลงในตาราง B อย่างเป็นอะตอม) ก็ต่อเมื่อมีซอฟต์แวร์สนับสนุนที่ค่อนข้างซับซ้อน (มีภาระงานสูง) และการเขียนโปรแกรมแอปพลิเคชันต้องปฏิบัติตามข้อกำหนดที่เข้มงวดอย่างสมบูรณ์แบบ
- การผกผันลำดับความสำคัญ : เธรด/กระบวนการที่มีลำดับความสำคัญต่ำที่ถือล็อกร่วมกันอาจขัดขวางไม่ให้เธรด/กระบวนการที่มีลำดับความสำคัญสูงดำเนินการต่อไปได้การสืบทอดลำดับความสำคัญสามารถใช้เพื่อลดระยะเวลาของการผกผันลำดับความสำคัญโปรโตคอลการจำกัดลำดับความสำคัญสามารถใช้ในระบบโปรเซสเซอร์เดี่ยวเพื่อลดระยะเวลาการผกผันลำดับความสำคัญในกรณีที่เลวร้ายที่สุด รวมถึงป้องกันภาวะติดตาย ได้
- การรอคิว : เธรดอื่นๆ ทั้งหมดจะต้องรอหากเธรดที่ถือล็อกอยู่ถูกยกเลิกการทำงานเนื่องจากการขัดจังหวะช่วงเวลาหรือข้อผิดพลาดในการเข้าถึงหน่วยความจำ
กลยุทธ์ การควบคุมการทำงานพร้อมกันบางอย่างสามารถหลีกเลี่ยงปัญหาเหล่านี้ได้บางส่วนหรือทั้งหมด ตัวอย่างเช่นการใช้ช่องทาง (funnel)หรือการจัดลำดับโทเค็น (serializing tokens)สามารถหลีกเลี่ยงปัญหาที่ใหญ่ที่สุดได้ นั่นคือ การติดตาย (deadlock) ทางเลือกอื่นนอกเหนือจากการล็อก ได้แก่ วิธี การซิงโครไนซ์แบบไม่บล็อกเช่น เทคนิคการเขียนโปรแกรมแบบไร้ ล็อก (lock-free programming techniques) และหน่วยความจำแบบทำธุรกรรม (transactional memory ) อย่างไรก็ตาม วิธีการทางเลือกเหล่านี้มักต้องการให้กลไกการล็อกจริงถูกนำไปใช้ในระดับพื้นฐานกว่าของซอฟต์แวร์ปฏิบัติการ ดังนั้นจึงอาจช่วยลดภาระ ในระดับ แอปพลิเคชันจากรายละเอียดของการใช้งานล็อกเท่านั้น โดยปัญหาที่กล่าวมาข้างต้นยังคงต้องได้รับการจัดการในระดับที่ต่ำกว่าแอปพลิเคชัน
ในกรณีส่วนใหญ่ การล็อกที่ถูกต้องนั้นขึ้นอยู่กับซีพียูที่จัดเตรียมวิธีการซิงโครไนซ์กระแสคำสั่งอะตอมิก (ตัวอย่างเช่น การเพิ่มหรือลบรายการในไปป์ไลน์จำเป็นต้องระงับการทำงานพร้อมกันทั้งหมดที่ต้องการเพิ่มหรือลบรายการอื่น ๆ ในไปป์ไลน์ในระหว่างการจัดการเนื้อหาหน่วยความจำที่จำเป็นสำหรับการเพิ่มหรือลบรายการเฉพาะนั้น) ดังนั้น แอปพลิเคชันจึงมักมีความแข็งแกร่งมากขึ้นเมื่อตระหนักถึงภาระที่มันสร้างให้กับระบบปฏิบัติการและสามารถรับรู้ถึงการรายงานความต้องการที่เป็นไปไม่ได้ได้อย่างราบรื่น
ขาดความสามารถในการประกอบ
ปัญหาใหญ่ที่สุดอย่างหนึ่งของการเขียนโปรแกรมแบบใช้ล็อกคือ "ล็อกไม่สามารถประกอบกันได้ " กล่าวคือ เป็นเรื่องยากที่จะรวมโมดูลแบบใช้ล็อกขนาดเล็กที่ถูกต้องเข้ากับโปรแกรมขนาดใหญ่ที่ถูกต้องเท่าเทียมกันโดยไม่ต้องแก้ไขโมดูลหรืออย่างน้อยก็ต้องรู้เกี่ยวกับกลไกภายในของโมดูลนั้นSimon Peyton Jones (ผู้สนับสนุนหน่วยความจำธุรกรรมซอฟต์แวร์ ) ยกตัวอย่างแอปพลิเคชันธนาคารดังต่อไปนี้: [ 6 ]ออกแบบคลาสAccountที่อนุญาตให้ไคลเอนต์หลายรายพร้อมกันฝากหรือถอนเงินเข้าบัญชี และกำหนดอัลกอริทึมในการโอนเงินจากบัญชีหนึ่งไปยังอีกบัญชีหนึ่ง
วิธีแก้ปัญหาในส่วนแรกโดยใช้กลไกการล็อกมีดังนี้:
คลาส Account: สมาชิก balance: จำนวนเต็ม สมาชิก mutex: ล็อก เมธอดฝาก (n: จำนวนเต็ม) มิวเท็กซ์ล็อก() ยอดคงเหลือ ← ยอดคงเหลือ + n mutex.unlock() เมธอดถอน (n: จำนวนเต็ม) เงินฝาก(−n)
ส่วนที่สองของปัญหาซับซ้อนกว่ามาก รูทีน การถ่ายโอนที่ถูกต้องสำหรับโปรแกรมแบบลำดับจะเป็นดังนี้
ฟังก์ชันโอนเงิน (จาก: บัญชี, ไปยัง: บัญชี, จำนวนเงิน: จำนวนเต็ม) ถอนเงินจาก (จำนวนเงิน) ฝากเงิน (จำนวน)
ในโปรแกรมที่ทำงานพร้อมกัน อัลกอริทึมนี้ไม่ถูกต้อง เพราะเมื่อเธรดหนึ่งกำลังดำเนินการโอนเงินไป ได้ครึ่งทาง อีกเธรดหนึ่งอาจสังเกตเห็นสถานะที่เงินถูกถอนออกจากบัญชีแรกแล้ว แต่ยังไม่ได้ฝากเข้าบัญชีอื่น ทำให้เงินหายไปจากระบบ ปัญหานี้สามารถแก้ไขได้อย่างสมบูรณ์โดยการล็อกทั้งสองบัญชีก่อนที่จะเปลี่ยนแปลงบัญชีใดบัญชีหนึ่ง แต่การล็อกจะต้องถูกจัดวางตามลำดับที่กำหนดไว้ทั่วโลกเพื่อป้องกันภาวะติดตาย (deadlock):
ฟังก์ชัน transfer(from: Account, to: Account, amount: Integer) ถ้า from < to // ลำดับการล็อกตามอำเภอใจ จากล็อก() to.lock() อื่น to.lock() จากล็อก() ถอนเงินจาก (จำนวนเงิน) ฝากเงิน (จำนวน) จาก.ปลดล็อก() to.unlock()
วิธีการนี้จะซับซ้อนมากขึ้นเมื่อมีตัวล็อกหลายตัว และ ฟังก์ชัน การถ่ายโอนจำเป็นต้องทราบข้อมูลเกี่ยวกับตัวล็อกทั้งหมด ดังนั้นจึงไม่สามารถซ่อนตัวล็อกได้
การสนับสนุนด้านภาษา
ภาษาโปรแกรมแต่ละภาษามีระดับการรองรับการซิงโครไนซ์ที่แตกต่างกัน:
- Adaจัดเตรียมวัตถุที่ได้รับการป้องกันซึ่งมีซับโปรแกรมหรือรายการที่ได้รับการป้องกันที่มองเห็นได้[ 7 ]รวมถึงการนัดพบ[ 8 ]
- มาตรฐาน ISO/IEC C ให้ อินเทอร์เฟซการเขียนโปรแกรมแอปพลิเคชัน (API) มาตรฐานการยกเว้นร่วมกัน (ล็อค) ตั้งแต่C11ในส่วนหัวพร้อมฟังก์ชันการจัดการมิวเท็กซ์ต่างๆ[ 9 ]
<threads.h>- มาตรฐานOpenMPได้รับการสนับสนุนจากคอมไพเลอร์บางตัว และอนุญาตให้ระบุส่วนสำคัญ โดยใช้ pragmas ได้
- API pthread ของ POSIXให้การสนับสนุนการล็อก[ 10 ] C และ C++ สามารถเข้าถึงคุณสมบัติการล็อกของระบบปฏิบัติการดั้งเดิมได้อย่างง่ายดาย
- มาตรฐาน ISO/IEC C++ ปัจจุบัน รองรับฟังก์ชันการทำงานแบบมัลติเธรดตั้งแต่C++11 เป็นต้น มา โดยเฉพาะอย่างยิ่ง มีคลาส
std::mutexรวมถึงคลาสล็อกต่างๆ เช่นstd::lock_guardและstd::unique_lockในstd::scoped_lockส่วน<mutex>หัว[ 11 ]- Visual C++มี
synchronizeแอตทริบิวต์ของเมธอดที่จะซิงโครไนซ์ แต่แอตทริบิวต์นี้เฉพาะเจาะจงกับอ็อบเจ็กต์ COM ใน สถาปัตยกรรม Windowsและคอมไพเลอร์Visual C++ [ 12 ]
- Visual C++มี
- C#มี
lockคีย์เวิร์ดบนเธรดเพื่อให้แน่ใจว่าเธรดนั้นสามารถเข้าถึงทรัพยากรและSystem.Threading.Lockคลาส ได้อย่างเฉพาะเจาะจง [ 13 ] - Visual Basic (.NET)มี คีย์เวิร์ดคล้ายกับ คีย์เวิร์ด
SyncLockในภาษา C#lock - Javaมีคีย์เวิร์ด
synchronizedสำหรับล็อกบล็อกโค้ดเมธอดหรืออ็อบเจ็กต์[ 14 ]และไลบรารีที่มีโครงสร้างข้อมูลที่ปลอดภัยต่อการทำงานพร้อมกัน Java ยังมีอินเทอร์เฟซอีกjava.util.concurrent.locks.Lockด้วย[ 15 ] - Objective-Cมีคีย์เวิร์ด
@synchronized[ 16 ]เพื่อล็อกบล็อกของโค้ด และยังมีคลาส NSLock [ 17 ] NSRecursiveLock [ 18 ]และ NSConditionLock [ 19 ]พร้อมกับโปรโตคอล NSLocking [ 20 ]สำหรับการล็อกอีกด้วย - PHPมีการล็อกไฟล์[ 21 ]รวมถึง
Mutexคลาสในpthreadsส่วนขยาย[ 22 ] - Python มีกลไก mutexระดับต่ำด้วย
threading.Lockคลาส[ 23 ] - มาตรฐาน ISO/IEC Fortran (ISO/IEC 1539-1:2010) จัดเตรียม
lock_typeประเภทที่ได้มาในโมดูลภายในiso_fortran_envและ คำสั่งlock/unlockตั้งแต่Fortran 2008 [ 24 ] - Ruby มีออบเจ็กต์ mutexระดับต่ำและไม่มีคีย์เวิร์ด[ 25 ]
- Rustจัดเตรียมโครงสร้าง
std::sync::Mutex<T>[ 26 ] [ 27 ] - ภาษาแอสเซมบลี x86มี
LOCKคำนำหน้าสำหรับการดำเนินการบางอย่างเพื่อรับประกันความเป็นอะตอมิกของการดำเนินการเหล่านั้น - Haskellใช้โครงสร้างข้อมูลที่เปลี่ยนแปลงได้ที่เรียกว่า
MVarซึ่งสามารถว่างเปล่าหรือมีค่า โดยทั่วไปจะเป็นการอ้างอิงถึงทรัพยากร เธรดที่ต้องการใช้ทรัพยากรจะ 'รับ' ค่าของ โดยMVarปล่อยให้ว่างเปล่า และส่งกลับเมื่อเสร็จสิ้น การพยายามรับทรัพยากรจาก ที่ว่างเปล่าMVarจะทำให้เธรดถูกบล็อกจนกว่าทรัพยากรจะพร้อมใช้งาน[ 28 ]นอกจากการล็อกแล้วยังมี การใช้งาน หน่วยความจำธุรกรรมซอฟต์แวร์ อีกด้วย [ 29 ] - Go มี อ็อบเจ็กต์Mutexระดับต่ำ
sync.Mutexในแพ็กเกจ sync ของไลบรารีมาตรฐาน [ 30 ]สามารถใช้สำหรับการล็อกบล็อกโค้ดเมธอดหรืออ็อบเจ็กต์ได้
มิวเท็กซ์กับเซมาฟอร์
มิวเท็กซ์ (Mutex)คือกลไกการล็อกที่บางครั้งใช้การใช้งานพื้นฐานเดียวกันกับเซมาฟอร์แบบไบนารี (Binary Semaphore) อย่างไรก็ตาม วิธีการใช้งานนั้นแตกต่างกัน ในขณะที่เซมาฟอร์แบบไบนารีอาจถูกเรียกกันทั่วไปว่ามิวเท็กซ์ แต่จริงๆ แล้วมิวเท็กซ์มีกรณีการใช้งานและคำจำกัดความที่เฉพาะเจาะจงกว่า กล่าวคือ มีเพียงงานที่ล็อกมิวเท็กซ์เท่านั้นที่จะสามารถปลดล็อกได้ ข้อจำกัดนี้มีจุดมุ่งหมายเพื่อจัดการกับปัญหาที่อาจเกิดขึ้นจากการใช้เซมาฟอร์:
- การกลับลำดับความสำคัญ : หากมิวเท็กซ์รู้ว่าใครเป็นผู้ล็อกและควรเป็นผู้ปลดล็อก ก็เป็นไปได้ที่จะเพิ่มลำดับความสำคัญของงานนั้นเมื่อใดก็ตามที่งานที่มีลำดับความสำคัญสูงกว่าเริ่มรอใช้งานมิวเท็กซ์
- การยุติงานก่อนกำหนด: มิวเท็กซ์อาจให้ความปลอดภัยในการลบได้เช่นกัน โดยที่งานที่ถือมิวเท็กซ์จะไม่สามารถถูกลบโดยไม่ตั้งใจได้ (นี่ก็เป็นต้นทุนเช่นกัน หากมิวเท็กซ์สามารถป้องกันไม่ให้งานถูกเรียกคืนได้ ตัวเก็บขยะจะต้องตรวจสอบมิวเท็กซ์นั้น)
- ภาวะติดตายเมื่อสิ้นสุดการทำงาน: หากงานที่ถือครองมิวเท็กซ์สิ้นสุดลงด้วยเหตุผลใดก็ตามระบบปฏิบัติการสามารถปล่อยมิวเท็กซ์และส่งสัญญาณไปยังงานที่รออยู่เกี่ยวกับสภาวะนี้ได้
- การติดตายแบบเรียกซ้ำ: งานหนึ่งสามารถล็อกมิวเท็กซ์แบบเรียกซ้ำได้หลายครั้ง ในขณะที่อีกงานหนึ่งปลดล็อกมิวเท็กซ์นั้นจำนวนครั้งเท่ากัน
- การปล่อยโดยไม่ตั้งใจ: จะเกิดข้อผิดพลาดเมื่อปล่อย mutex หากงานที่ปล่อยไม่ใช่เจ้าของ mutex นั้น
ดูเพิ่มเติม
- ส่วนวิกฤต
- ตรวจสอบการล็อกซ้ำอีกครั้ง
- การล็อกไฟล์
- อัลกอริทึมแบบไม่ต้องล็อกและไม่ต้องรอคอย
- มอนิเตอร์ (การซิงโครไนซ์)
- การกีดกันซึ่งกันและกัน
- รูปแบบการอ่าน/เขียนล็อค
ลิงก์ภายนอก
- คู่มือเกี่ยวกับกลไกการล็อกและส่วนวิกฤต
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ ล็อค (วิทยาการคอมพิวเตอร์)
ในวิทยาการคอมพิวเตอร์ล็อกหรือมิวเท็กซ์ (มาจากหลักการกีดกันร่วมกัน ) คือกลไกการซิงโครไนซ์ ที่ป้องกันไม่ให้เธรดการ ทำงานหลายเธรดแก้ไขหรือเข้าถึงสถานะพร้อมกัน ล็อกบังคับใช้หลักการ
ประเภท
โดยทั่วไปแล้ว ล็อกจะเป็น ล็อกแบบให้คำแนะนำ ซึ่งแต่ละเธรดจะให้ความร่วมมือโดยการขอรับล็อกก่อนที่จะเข้าถึงข้อมูลที่เกี่ยวข้อง บางระบบยังใช้ ล็อกแบบบังคับ ซึ่งการพยายามเข้าถึงทรัพยากรที่ถูกล็อกโดยไม่ได้รับอนุญาตจะทำให้เกิด ข้อผิดพลาด ในเอนทิตีที่พยายามเข้าถึงนั้น
ความละเอียด
ก่อนที่จะทำความเข้าใจเกี่ยวกับระดับความละเอียดของการล็อก (lock granularity) จำเป็นต้องเข้าใจแนวคิดพื้นฐานสามประการเกี่ยวกับการล็อกก่อน:
การล็อกฐานข้อมูล
การล็อกฐานข้อมูล สามารถใช้เป็นวิธีการเพื่อให้มั่นใจถึงความสอดคล้องกันของธุรกรรม กล่าวคือ เมื่อทำการประมวลผลธุรกรรมพร้อมกัน (การสลับธุรกรรม) การใช้ การล็อกแบบ 2 ขั้นตอน จะช่วยให้การดำเนินการพร้อมกันของธุรกรรมนั้นเทียบเท่ากับการดำเนินการตามลำดับแบบอนุกรม...