สมมติว่าเธรดเหล่านี้ทำงานในซีพียูแกนเดียว ในฐานะที่เป็นซีพียูทำงานเพียงคำสั่งเดียวในรอบ แม้จะคิดว่าพวกเขาแบ่งปันทรัพยากรซีพียู แต่คอมพิวเตอร์รับรองว่าครั้งเดียวหนึ่งคำสั่ง ดังนั้นล็อคไม่จำเป็นสำหรับเธรดหลาย ๆ
สมมติว่าเธรดเหล่านี้ทำงานในซีพียูแกนเดียว ในฐานะที่เป็นซีพียูทำงานเพียงคำสั่งเดียวในรอบ แม้จะคิดว่าพวกเขาแบ่งปันทรัพยากรซีพียู แต่คอมพิวเตอร์รับรองว่าครั้งเดียวหนึ่งคำสั่ง ดังนั้นล็อคไม่จำเป็นสำหรับเธรดหลาย ๆ
คำตอบ:
นี่คือตัวอย่างที่ดีที่สุดด้วยตัวอย่าง
สมมติว่าเรามีงานง่าย ๆ ที่เราต้องการดำเนินการหลาย ๆ ครั้งในแบบคู่ขนานและเราต้องการที่จะติดตามทั่วโลกในจำนวนครั้งที่มีการดำเนินงานเช่นการนับจำนวนการเข้าชมบนหน้าเว็บ
เมื่อแต่ละเธรดมาถึงจุดที่มันเพิ่มจำนวนการประมวลผลจะมีลักษณะดังนี้:
โปรดจำไว้ว่าทุกเธรดสามารถระงับได้ทุกเวลาในกระบวนการนี้ ดังนั้นหากเธรด A ดำเนินการตามขั้นตอนที่ 1 และจากนั้นถูกระงับตามด้วยเธรด B ที่ดำเนินการทั้งสามขั้นตอนเมื่อดำเนินการต่อเธรด A การลงทะเบียนจะมีจำนวนครั้งที่ไม่ถูกต้อง: การลงทะเบียนของมันจะถูกคืนค่าอย่างมีความสุข จาก Hit และเก็บหมายเลขที่เพิ่มขึ้นนั้น
นอกจากนี้จำนวนเธรดอื่นใด ๆ ที่สามารถรันได้ในช่วงเวลาที่เธรด A ถูกหยุดชั่วคราวดังนั้นการนับเธรด A ที่เขียนในตอนท้ายอาจต่ำกว่าการนับที่ถูกต้อง
ด้วยเหตุผลดังกล่าวจึงจำเป็นต้องตรวจสอบให้แน่ใจว่าหากเธรดดำเนินการขั้นตอนที่ 1 จะต้องดำเนินการตามขั้นตอนที่ 3 ก่อนที่จะอนุญาตให้เธรดอื่นดำเนินการขั้นตอนที่ 1 ซึ่งสามารถทำได้โดยเธรดทั้งหมดที่รอการล็อกเดี่ยว และการปลดล็อกเฉพาะหลังจากกระบวนการเสร็จสิ้นเพื่อให้โค้ด "ส่วนที่สำคัญ" นี้ไม่สามารถถูกอินเตอร์ลีลอย่างไม่ถูกต้องทำให้เกิดการนับที่ไม่ถูกต้อง
แต่ถ้าปฏิบัติการเป็นอะตอม
ใช่ในดินแดนแห่งยูนิคอร์นที่มีมนต์ขลังและสายรุ้งที่ซึ่งการเพิ่มขึ้นของอะตอมนั้นจะไม่จำเป็นต้องล็อคตัวอย่างด้านบน
อย่างไรก็ตามสิ่งสำคัญคือต้องตระหนักว่าเราใช้เวลาน้อยมากในโลกของยูนิคอร์นและสายรุ้งที่มีมนต์ขลัง ในเกือบทุกภาษาการเขียนโปรแกรมการดำเนินการเพิ่มขึ้นจะแบ่งออกเป็นสามขั้นตอนข้างต้น นั่นเป็นเพราะแม้ว่าโปรเซสเซอร์สนับสนุนการเพิ่มขึ้นของอะตอมการดำเนินการนั้นมีราคาแพงกว่าอย่างมาก: มันต้องอ่านจากหน่วยความจำปรับเปลี่ยนหมายเลขแล้วเขียนมันกลับไปที่หน่วยความจำ ... และโดยปกติแล้วการเพิ่มขึ้นของอะตอมคือการดำเนินการ สามารถล้มเหลวได้ซึ่งหมายถึงลำดับที่ง่าย ๆ ข้างต้นจะต้องถูกแทนที่ด้วยการวนซ้ำ (ดังที่เราจะเห็นด้านล่าง)
ตั้งแต่แม้ในรหัสแบบมัลติเธรดตัวแปรหลายตัวถูกเก็บไว้ในโลคัลเธรดเดียวโปรแกรมจะมีประสิทธิภาพมากขึ้นหากพวกเขาคิดว่าแต่ละตัวแปรเป็นแบบโลคัลสำหรับเธรดเดี่ยวและให้โปรแกรมเมอร์ดูแลการปกป้องสถานะที่แบ่งใช้ระหว่างเธรด โดยเฉพาะอย่างยิ่งเนื่องจากการปฏิบัติการปรมาณูไม่เพียงพอที่จะแก้ปัญหาการทำเกลียวเช่นที่เราจะเห็นในภายหลัง
ตัวแปรระเหย
หากเราต้องการหลีกเลี่ยงการล็อคสำหรับปัญหานี้ก่อนอื่นเราต้องตระหนักว่าขั้นตอนที่อธิบายไว้ในตัวอย่างแรกของเรานั้นไม่ใช่สิ่งที่เกิดขึ้นในคอมไพล์โค้ดที่ทันสมัย เนื่องจากคอมไพเลอร์คิดว่ามีเพียงเธรดเดียวเท่านั้นที่กำลังแก้ไขตัวแปรแต่ละเธรดจะเก็บสำเนาของแคชไว้ในตัวตัวแปรจนกว่าจะต้องลงทะเบียนตัวประมวลผลสำหรับสิ่งอื่น ตราบใดที่มันมีสำเนาแคชก็ถือว่าไม่จำเป็นต้องกลับไปที่หน่วยความจำและอ่านอีกครั้ง (ซึ่งจะมีราคาแพง) พวกเขาจะไม่เขียนตัวแปรกลับไปยังหน่วยความจำตราบใดที่มันถูกเก็บไว้ในทะเบียน
เราสามารถกลับไปสู่สถานการณ์ที่เราให้ไว้ในตัวอย่างแรก (ด้วยปัญหาการเธรดเดียวกับที่เราระบุข้างต้น) โดยการทำเครื่องหมายตัวแปรเป็นสารระเหยซึ่งบอกคอมไพเลอร์ว่าตัวแปรนี้ถูกแก้ไขโดยผู้อื่นและต้องอ่านจาก หรือเขียนไปยังหน่วยความจำทุกครั้งที่มีการเข้าถึงหรือแก้ไข
ดังนั้นตัวแปรที่ระบุว่าเป็นสารระเหยจะไม่นำเราไปสู่ดินแดนแห่งการเพิ่มขึ้นของอะตอมเพียง แต่ทำให้เราเข้าใกล้เท่าที่เราคิด
ทำให้อะตอมเพิ่มขึ้น
เมื่อเราใช้ตัวแปรที่เปลี่ยนแปลงได้เราสามารถสร้างการทำงานแบบเพิ่มหน่วยของเราโดยใช้ชุดปฏิบัติการแบบมีเงื่อนไขระดับต่ำซึ่งซีพียูสมัยใหม่ส่วนใหญ่รองรับ (มักเรียกว่าการเปรียบเทียบและการตั้งค่าหรือการเปรียบเทียบและสลับ ) วิธีนี้ถูกนำมาใช้เช่นในคลาสAtomicIntegerของ Java :
197 /**
198 * Atomically increments by one the current value.
199 *
200 * @return the updated value
201 */
202 public final int incrementAndGet() {
203 for (;;) {
204 int current = get();
205 int next = current + 1;
206 if (compareAndSet(current, next))
207 return next;
208 }
209 }
วนซ้ำด้านบนทำตามขั้นตอนต่อไปนี้ซ้ำ ๆ จนกระทั่งขั้นตอนที่ 3 สำเร็จ:
หากขั้นตอนที่ 3 ล้มเหลว (เนื่องจากค่าถูกเปลี่ยนโดยเธรดอื่นหลังจากขั้นตอนที่ 1) มันจะอ่านตัวแปรอีกครั้งโดยตรงจากหน่วยความจำหลักและลองอีกครั้ง
แม้ว่าการดำเนินการเปรียบเทียบและสลับจะมีราคาแพงกว่าการใช้การล็อกเล็กน้อยในกรณีนี้เพราะถ้าเธรดถูกหยุดชั่วคราวหลังจากขั้นตอนที่ 1 เธรดอื่นที่มาถึงขั้นตอนที่ 1 ไม่จำเป็นต้องบล็อกและรอเธรดแรกซึ่ง สามารถป้องกันการสลับบริบทค่าใช้จ่าย เมื่อเธรดแรกดำเนินการต่อมันจะล้มเหลวในความพยายามครั้งแรกในการเขียนตัวแปร แต่จะสามารถดำเนินการต่อโดยการอ่านตัวแปรอีกครั้งซึ่งมีโอกาสน้อยกว่าการเปลี่ยนบริบทที่จะต้องใช้ในการล็อกอีกครั้ง
ดังนั้นเราสามารถไปถึงดินแดนของการเพิ่มขึ้นของอะตอม (หรือการดำเนินการอื่น ๆ ในตัวแปรเดียว) โดยไม่ต้องใช้การล็อคจริงผ่านการเปรียบเทียบและสลับ
ดังนั้นเมื่อจำเป็นต้องล็อคอย่างเคร่งครัด?
หากคุณต้องการแก้ไขตัวแปรมากกว่าหนึ่งตัวในการทำงานแบบปรมาณูการล็อกจะจำเป็นคุณจะไม่พบคำสั่งตัวประมวลผลพิเศษสำหรับสิ่งนั้น
ตราบใดที่คุณทำงานกับตัวแปรตัวเดียวและคุณก็พร้อมสำหรับงานที่คุณทำเพื่อล้มเหลวและต้องอ่านตัวแปรและเริ่มต้นใหม่อีกครั้งการเปรียบเทียบและการสลับจะดีพอ
ลองพิจารณาตัวอย่างที่แต่ละเธรดเพิ่ม 2 เข้ากับตัวแปร X ก่อนแล้วจึงคูณ X ด้วยสอง
ถ้า X เป็นหนึ่งในตอนแรกและมีสองเธรดที่ทำงานอยู่เราคาดว่าผลลัพธ์จะเป็น (((1 + 2) * 2) + 2) * 2 = 16
อย่างไรก็ตามถ้าเธรดสอดแทรกเราสามารถทำได้แม้จะมีการดำเนินการทั้งหมดเป็นอะตอม แต่ก็มีการเพิ่มทั้งคู่เกิดขึ้นก่อนและการคูณจะเกิดขึ้นตามมาทำให้เกิด (1 + 2 + 2) * 2 * 2 = 20
สิ่งนี้เกิดขึ้นเนื่องจากการคูณและการเพิ่มไม่ใช่การดำเนินการสลับ
ดังนั้นการดำเนินการของตัวเองที่เป็นปรมาณูไม่เพียงพอเราต้องทำให้การรวมกันของการดำเนินงานปรมาณู
เราสามารถทำได้โดยใช้การล็อกเพื่อทำให้กระบวนการเป็นอนุกรมหรือเราสามารถใช้ตัวแปรโลคัลหนึ่งตัวเพื่อเก็บค่าของ X เมื่อเราเริ่มการคำนวณของเราตัวแปรโลคัลที่สองสำหรับขั้นตอนกลางจากนั้นใช้การเปรียบเทียบและสลับเพื่อ กำหนดค่าใหม่เฉพาะในกรณีที่ค่าปัจจุบันของ X เท่ากับค่าเดิมของ X หากเราล้มเหลวเราจะต้องเริ่มต้นใหม่อีกครั้งโดยการอ่าน X และทำการคำนวณอีกครั้ง
มีการแลกเปลี่ยนหลายอย่างที่เกี่ยวข้อง: เมื่อการคำนวณนานขึ้นมีแนวโน้มมากขึ้นที่เธรดที่กำลังรันจะถูกระงับและค่าจะถูกแก้ไขโดยเธรดอื่นก่อนที่เราจะดำเนินการต่อความหมายของความล้มเหลวมีแนวโน้มมากขึ้น เวลาประมวลผล ในกรณีที่รุนแรงของเธรดจำนวนมากที่มีการคำนวณที่ใช้เวลานานมากเราอาจมี 100 เธรดอ่านตัวแปรและมีส่วนร่วมในการคำนวณในกรณีนี้เฉพาะการจบครั้งแรกเท่านั้นที่จะประสบความสำเร็จในการเขียนค่าใหม่ ทำการคำนวณให้เสร็จ แต่พบว่าพวกเขาไม่สามารถอัปเดตค่า ... ณ จุดที่พวกเขาแต่ละคนจะอ่านค่าและเริ่มการคำนวณ เราน่าจะมี 99 กระทู้ที่เหลือทำซ้ำปัญหาเดียวกันทำให้เสียเวลาในการประมวลผลจำนวนมหาศาล
การทำให้เป็นอนุกรมอย่างสมบูรณ์ของส่วนที่สำคัญผ่านการล็อคจะดีกว่ามากในสถานการณ์นั้น: 99 เธรดจะระงับเมื่อพวกเขาไม่ได้รับการล็อคและเราจะเรียกใช้แต่ละเธรดเพื่อให้มาถึงที่จุดล็อค
หากการทำให้เป็นอนุกรมไม่สำคัญ (เช่นในกรณีที่เพิ่มขึ้นของเรา) และการคำนวณที่จะหายไปหากการอัพเดทหมายเลขล้มเหลวมีน้อยที่สุดอาจมีข้อได้เปรียบที่สำคัญที่จะได้รับจากการใช้การเปรียบเทียบและสลับเนื่องจากการดำเนินการนั้น แพงน้อยกว่าการล็อค
พิจารณาคำพูดนี้:
บางคนเมื่อต้องเผชิญกับปัญหาคิดว่า "ฉันรู้ว่าฉันจะใช้หัวข้อ" แล้วพวกเขาทั้งสองมี poblesms haver
คุณจะเห็นว่าแม้ว่าการเรียนการสอน 1 ครั้งจะทำงานบน CPU ในเวลาใดก็ตามโปรแกรมคอมพิวเตอร์ประกอบด้วยมากกว่าคำแนะนำในการประกอบอะตอมมิก ตัวอย่างเช่นการเขียนไปยังคอนโซล (หรือไฟล์) หมายความว่าคุณต้องล็อคเพื่อให้แน่ใจว่ามันทำงานได้อย่างที่คุณต้องการ
ดูเหมือนว่ามีคำตอบมากมายที่พยายามอธิบายการล็อก แต่ฉันคิดว่าสิ่งที่ OP ต้องการคือคำอธิบายว่ามัลติทาสกิ้งคืออะไร
เมื่อคุณมีมากกว่าหนึ่งเธรดที่ทำงานบนระบบแม้จะมีหนึ่ง CPU มีวิธีการหลักสองวิธีที่กำหนดว่าเธรดเหล่านี้จะถูกกำหนดเวลาไว้อย่างไร (เช่นถูกวางให้ทำงานในซีพียูแบบแกนเดี่ยว):
ปัญหาไม่ได้อยู่ที่การปฏิบัติงานของแต่ละบุคคล
อัลกอริธึมจำนวนมากถูกเขียนขึ้นโดยมีสมมติฐานว่าพวกเขาสามารถควบคุมสถานะที่พวกเขาใช้งานได้อย่างเต็มที่ ด้วยรูปแบบการดำเนินการตามคำสั่งแบบอินเตอร์ลีฟเช่นเดียวกับที่คุณอธิบายการดำเนินการอาจมีความสอดประสานกันโดยพลการและถ้าพวกเขาแบ่งปันสถานะจะมีความเสี่ยงที่รัฐจะอยู่ในสภาพที่ไม่สอดคล้องกัน
คุณสามารถเปรียบเทียบกับฟังก์ชั่นที่อาจทำลายค่าคงที่ชั่วคราวเพื่อทำสิ่งที่พวกเขาทำ ตราบใดที่สถานะตัวกลางไม่สามารถสังเกตได้จากภายนอกพวกเขาสามารถทำสิ่งที่พวกเขาต้องการเพื่อให้บรรลุภารกิจของพวกเขา
เมื่อคุณเขียนรหัสที่เกิดขึ้นพร้อมกันคุณจะต้องตรวจสอบให้แน่ใจว่าสถานะที่โต้แย้งนั้นถือว่าไม่ปลอดภัยเว้นแต่คุณจะสามารถเข้าถึงแบบพิเศษได้ วิธีทั่วไปในการเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคลนั้นคือการซิงโครไนซ์กับการซิงโครไนซ์ดั้งเดิมเช่นการล็อค
อีกสิ่งหนึ่งที่การซิงโครไนซ์แบบดั้งเดิมมีแนวโน้มที่จะส่งผลให้ในบางแพลตฟอร์มคือพวกมันปล่อยสิ่งกีดขวางหน่วยความจำซึ่งทำให้มั่นใจได้ถึงความสอดคล้องของหน่วยความจำระหว่างซีพียู
ยกเว้นการตั้งค่า 'บูล' ไม่มีการรับประกัน (อย่างน้อยใน c) ว่าการอ่านหรือการเขียนตัวแปรใช้คำสั่งเดียวเท่านั้น - หรือไม่สามารถถูกขัดจังหวะในระหว่างการอ่าน / เขียน
bool
มีคุณสมบัตินี้ได้อย่างไร และคุณกำลังพูดถึงการโหลดจากหน่วยความจำการเปลี่ยนแปลงและการผลักกลับไปที่หน่วยความจำหรือคุณกำลังพูดถึงในระดับการลงทะเบียน? การอ่าน / เขียนไปยังการลงทะเบียนทั้งหมดจะไม่ถูกขัดจังหวะ แต่การโหลด mem จากนั้นที่เก็บ mem จะไม่ (เนื่องจากคำสั่งเพียงอย่างเดียวคือ 2 คำแนะนำจากนั้นอย่างน้อย 1 การเปลี่ยนแปลงค่า)
the standard says that only 'bool' needs to be safe against a context switch in the middle of a read/write of a single variable
ควรเพิ่มคำอธิบายลงในคำตอบจริงๆ
แชร์หน่วยความจำ
มันเป็นคำจำกัดความของ ... กระทู้ : พวงของกระบวนการที่เกิดขึ้นพร้อมกันกับหน่วยความจำที่ใช้ร่วมกัน
หากไม่มีการใช้ร่วมกันหน่วยความจำที่พวกเขามักจะเรียกว่าเป็นโรงเรียนเก่า-UNIXกระบวนการ
พวกเขาอาจต้องล็อคตอนนี้และเมื่อเข้าถึงไฟล์ที่ใช้ร่วมกัน
(หน่วยความจำที่ใช้ร่วมกันในเคอร์เนลที่คล้าย UNIX โดยปกติแล้วจะใช้งานโดยใช้ file descriptor ปลอมแทนที่อยู่หน่วยความจำที่ใช้ร่วมกัน)
CPU รันหนึ่งคำสั่งพร้อมกัน แต่จะเกิดอะไรขึ้นถ้าคุณมี CPU สองตัวขึ้นไป
คุณมีสิทธิ์ในการล็อคที่ไม่จำเป็นถ้าคุณสามารถเขียนโปรแกรมเพื่อใช้ประโยชน์จากคำสั่งอะตอม: คำแนะนำที่การดำเนินการไม่ถูกขัดจังหวะในโปรเซสเซอร์ที่กำหนดและปราศจากการแทรกแซงจากโปรเซสเซอร์อื่น ๆ
ต้องมีการล็อคเมื่อจำเป็นต้องป้องกันหลายคำแนะนำจากสัญญาณรบกวนและไม่มีคำสั่งอะตอมมิกเทียบเท่า
ตัวอย่างเช่นการแทรกโหนดในรายการที่ลิงก์เป็นสองเท่าต้องมีการอัพเดตตำแหน่งหน่วยความจำหลายแห่ง ก่อนการแทรกและหลังการแทรกค่าคงที่บางค่าคงที่เกี่ยวกับโครงสร้างของรายการ อย่างไรก็ตามในระหว่างการแทรกค่าคงที่เหล่านั้นจะถูกทำลายชั่วคราว: รายการอยู่ในสถานะ "ภายใต้การก่อสร้าง"
หากเธรดอื่นเดินผ่านรายการในขณะที่ค่าคงที่หรือพยายามแก้ไขเมื่อสถานะเป็นเช่นนั้นโครงสร้างข้อมูลอาจเสียหายและพฤติกรรมไม่สามารถคาดเดาได้: ซอฟต์แวร์อาจทำงานผิดพลาดหรืออาจดำเนินการต่อด้วยผลลัพธ์ที่ไม่ถูกต้อง ดังนั้นจึงเป็นเรื่องจำเป็นสำหรับเธรดที่ตกลงกันว่าจะหลีกทางให้กันเมื่อรายการถูกอัพเดต
รายการที่ได้รับการออกแบบอย่างเหมาะสมสามารถจัดการได้ด้วยคำแนะนำอะตอมมิกดังนั้นจึงไม่จำเป็นต้องล็อค อัลกอริทึมสำหรับสิ่งนี้เรียกว่า "ล็อคฟรี" อย่างไรก็ตามโปรดทราบว่าคำแนะนำอะตอมเป็นรูปแบบของการล็อคจริง มีการใช้งานเป็นพิเศษในฮาร์ดแวร์และทำงานผ่านการสื่อสารระหว่างโปรเซสเซอร์ พวกเขามีราคาแพงกว่าคำแนะนำที่คล้ายกันซึ่งไม่ใช่อะตอม
บนมัลติโปรเซสเซอร์ที่ไม่มีคำสั่งปรมาณูขั้นพื้นฐานสำหรับการยกเว้นซึ่งกันและกันจะต้องสร้างการเข้าถึงหน่วยความจำอย่างง่ายและการวนซ้ำแบบสำรวจ ปัญหาดังกล่าวเกิดขึ้นโดย Edsger Dijkstra และ Leslie Lamport