สภาพการแข่งขันคืออะไร?


982

เมื่อเขียนแอปพลิเคชันแบบมัลติเธรดหนึ่งในปัญหาที่พบบ่อยที่สุดคือสภาพการแข่งขัน

คำถามของฉันต่อชุมชนคือ:

สภาพการแข่งขันคืออะไร?
คุณตรวจจับพวกมันได้อย่างไร
คุณจัดการกับมันอย่างไร
สุดท้ายคุณจะป้องกันไม่ให้เกิดขึ้นได้อย่างไร


3
มีบทที่ยอดเยี่ยมในการเขียนโปรแกรมความปลอดภัยสำหรับ Linux HOWTOที่อธิบายถึงสิ่งที่พวกเขาเป็นและวิธีการหลีกเลี่ยงพวกเขา
Craig H

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

@MikeMB เห็นด้วยยกเว้นเมื่อวิเคราะห์การประมวลผลรหัสไบต์เหมือนกับที่ทำโดย Race Catcher (ดูเธรดนี้stackoverflow.com/a/29361427/1363844 ) เราสามารถจัดการกับภาษาทั้งหมด 62 ภาษาที่คอมไพล์เป็นรหัสไบต์ (ดูen.wikipedia.org / wiki / List_of_JVM_languages )
Ben

คำตอบ:


1237

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

ปัญหามักจะเกิดขึ้นเมื่อเธรดหนึ่ง "ตรวจสอบแล้วดำเนินการ" (เช่น "ตรวจสอบ" ถ้าค่าเป็น X จากนั้น "กระทำ" เพื่อทำสิ่งที่ขึ้นอยู่กับค่าเป็น X) และหัวข้ออื่นทำบางสิ่งบางอย่างให้เป็นค่าใน ระหว่าง "ตรวจสอบ" และ "การกระทำ" เช่น:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

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

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

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

121
เธรดอื่นทำอะไรเมื่อพบการล็อก รอเหรอ? ข้อผิดพลาด?
Brian Ortiz

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

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

2
@Despertar ... ยังไม่จำเป็นว่าในกรณีที่ทรัพยากรจะต้องมีการใช้ร่วมกันในระบบ milti-threaded ตัวอย่างเช่นคุณอาจมีอาร์เรย์ที่แต่ละองค์ประกอบต้องการการประมวลผล คุณอาจแบ่งพาร์ติชันอาร์เรย์และมีเธรดสำหรับแต่ละพาร์ติชันและเธรดสามารถทำงานได้อย่างสมบูรณ์โดยอิสระจากกัน
Ian Warburton

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

213

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

ใช้ตัวอย่างนี้:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

หากคุณมี 5 เธรดที่เรียกใช้โค้ดนี้ในครั้งเดียวค่าของ x WOULD จะไม่สิ้นสุดที่ 50,000,000 ในความเป็นจริงมันจะแตกต่างกันไปในแต่ละครั้ง

นี่เป็นเพราะเพื่อให้แต่ละเธรดเพื่อเพิ่มค่าของ x พวกเขาต้องทำดังต่อไปนี้: (ง่าย, ชัดแจ้ง)

ดึงค่าของ x
เพิ่ม 1 เข้ากับค่านี้
เก็บค่านี้ไว้ที่ x

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

สมมติว่าเธรดดึงค่าของ x แต่ยังไม่ได้เก็บไว้ เธรดอื่นสามารถดึงค่าเดียวกันของ x (เนื่องจากยังไม่มีเธรดเปลี่ยนไป) จากนั้นทั้งคู่ก็จะเก็บค่าเดียวกัน (x + 1) กลับมาเป็น x!

ตัวอย่าง:

เธรด 1: อ่าน x ค่าคือ 7
เธรด 1: เพิ่ม 1 ถึง x ตอนนี้ค่าเป็น 8
เธรด 2: อ่าน x ค่าคือ 7
หัวข้อที่ 1: เก็บ 8 ใน x
เธรด 2: เพิ่ม 1 ถึง x ตอนนี้ค่าคือ 8
หัวข้อที่ 2: เก็บ 8 ใน x

เงื่อนไขการแย่งชิงสามารถหลีกเลี่ยงได้โดยใช้กลไกการล็อคบางประเภทก่อนรหัสที่เข้าถึงทรัพยากรที่ใช้ร่วมกัน:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

ที่นี่คำตอบออกมาเป็น 50,000,000 ทุกครั้ง

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการล็อกให้ค้นหา: mutex, เซมาฟอร์, ส่วนที่สำคัญ, ทรัพยากรที่ใช้ร่วมกัน


ดูjakob.engbloms.se/archives/65สำหรับตัวอย่างของโปรแกรมเพื่อทดสอบว่าสิ่งเหล่านี้เลวร้ายเพียงใด ... มันขึ้นอยู่กับรุ่นของหน่วยความจำของเครื่องที่คุณใช้งานอยู่
jakobengblom2

1
จะไปถึง 50 ล้านได้อย่างไรถ้าต้องหยุดที่ 10 ล้าน

9
@nocomprende: โดย 5 กระทู้รันรหัสเดียวกันในเวลาตามที่อธิบายไว้ตรงด้านล่างของข้อมูลโค้ด ...
จอนสกีต

4
@ JonSkeet คุณพูดถูกฉันสับสน i และ the x ขอบคุณ.

การล็อกการตรวจสอบซ้ำในการใช้รูปแบบซิงเกิลเป็นตัวอย่างของการป้องกันสภาวะการแข่งขัน
Bharat Dodeja

150

เงื่อนไขการแข่งขันคืออะไร

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

คุณตรวจจับพวกมันได้อย่างไร

ตรวจสอบรหัสศาสนาการทดสอบหน่วยแบบมัลติเธรด ไม่มีทางลัด มีปลั๊กอิน Eclipse เกิดขึ้นเพียงเล็กน้อย แต่ยังไม่มีอะไรเสถียร

คุณจัดการและป้องกันพวกเขาอย่างไร?

สิ่งที่ดีที่สุดคือการสร้างฟังก์ชั่นฟรีและไร้สัญชาติโดยใช้ immutables ให้มากที่สุด แต่นั่นเป็นไปไม่ได้เสมอไป ดังนั้นการใช้ java.util.concurrent.atomic โครงสร้างข้อมูลที่เกิดขึ้นพร้อมกันการซิงโครไนซ์ที่เหมาะสมและการทำงานพร้อมกันของนักแสดงจะช่วยได้

ทรัพยากรที่ดีที่สุดสำหรับการเกิดพร้อมกันคือ JCIP นอกจากนี้คุณยังจะได้รับเพิ่มเติมบางส่วนรายละเอียดเกี่ยวกับคำอธิบายข้างต้นที่นี่


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

2
ฉันชื่นชมตัวอย่างโลกแห่งความจริงของสภาพการแข่งขัน
Tom O.

11
เช่นเดียวกับคำตอบที่นิ้วหัวแม่มือขึ้น ทางออกคือ: คุณล็อคตั๋วระหว่าง 4-5 ด้วย mutex (ข้อยกเว้นร่วม, c ++) ในโลกแห่งความจริงจะเรียกว่าการจองตั๋ว :)
โวลต์

1
จะเป็นคำตอบที่ดีถ้าคุณลดลงบิตจาวาเท่านั้น (เป็นคำถามที่ไม่เกี่ยวกับ Java แต่สภาพการแข่งขันทั่วไป)
คอเรย์โกลด์เบิร์ก

ไม่นี่ไม่ใช่เงื่อนไขการแข่งขัน จากมุมมอง "ธุรกิจ" คุณรอนานเกินไป เห็นได้ชัดว่า backorder ไม่ใช่วิธีแก้ปัญหา ลองใช้ scalper เป็นอย่างอื่นเพียงซื้อตั๋วเป็นประกัน
csherriff

65

มีความแตกต่างทางเทคนิคที่สำคัญระหว่างสภาพการแข่งขันและการแข่งขันของข้อมูล คำตอบส่วนใหญ่ดูเหมือนจะทำให้สมมติฐานที่ว่าเงื่อนไขเหล่านี้เทียบเท่า แต่ไม่ได้

การแย่งข้อมูลเกิดขึ้นเมื่อ 2 คำแนะนำเข้าถึงตำแหน่งหน่วยความจำเดียวกันอย่างน้อยหนึ่งในการเข้าถึงเหล่านี้คือการเขียนและจะไม่เกิดขึ้นก่อนที่จะสั่งซื้อระหว่างการเข้าถึงเหล่านี้ ตอนนี้สิ่งที่ถือว่าเกิดขึ้นก่อนที่จะสั่งซื้อจะต้องมีการถกเถียงกันมาก แต่โดยทั่วไปแล้วคู่ ulock-lock ในตัวแปรล็อกเดียวกันและคู่สัญญาณรอสัญญาณในตัวแปรสภาพเดียวกันทำให้เกิดคำสั่งที่เกิดขึ้นก่อน

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

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

ตอนนี้เราตอกคำศัพท์แล้วให้เราลองตอบคำถามเดิม

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

ในทางกลับกันการแข่งขันของข้อมูลมีคำจำกัดความที่แม่นยำซึ่งไม่จำเป็นต้องเกี่ยวข้องกับความถูกต้องดังนั้นจึงสามารถตรวจจับได้ มีหลายรสชาติของเครื่องตรวจจับการแข่งขันของข้อมูล (การตรวจจับการแข่งขันข้อมูลแบบคงที่ / แบบไดนามิก, การตรวจจับการแข่งขันข้อมูลที่ใช้ชุดล็อค, เกิดขึ้นก่อนการตรวจจับการแข่งขันข้อมูลตาม, การตรวจจับการแข่งขันข้อมูลแบบไฮบริด) เครื่องตรวจจับการแข่งขันข้อมูลแบบไดนามิกที่ทันสมัยที่สุดคือThreadSanitizerซึ่งทำงานได้ดีในทางปฏิบัติ

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


ความแตกต่างมีความสำคัญต่อการเข้าใจสภาพการแข่งขัน ขอบคุณ!
ProgramCpp

37

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


3
คุณช่วยยกตัวอย่างสภาพการแข่งขันที่มีประโยชน์ได้อย่างไร Googling ไม่ได้ช่วยอะไรเลย
Alex V.

3
@Alex V. ณ จุดนี้ฉันไม่รู้ว่าฉันกำลังพูดถึงอะไร ฉันคิดว่านี่อาจจะเป็นการอ้างอิงถึงการเขียนโปรแกรมล็อคฟรี แต่มันไม่ถูกต้องจริง ๆ ที่จะบอกว่าขึ้นอยู่กับสภาพการแข่งขันต่อ
Chris Conway

33

สภาพการแข่งขันเป็นข้อบกพร่องชนิดหนึ่งที่เกิดขึ้นกับเงื่อนไขทางโลกบางอย่างเท่านั้น

ตัวอย่าง: ลองนึกภาพคุณมีสองหัวข้อ A และ B

ในหัวข้อ A:

if( object.a != 0 )
    object.avg = total / object.a

ในกระทู้ B:

object.a = 0

หากเธรด A ถูกจองไว้หลังจากตรวจสอบว่า object.a ไม่ใช่โมฆะ B จะทำa = 0และเมื่อเธรด A จะได้รับตัวประมวลผลมันจะทำการ "หารด้วยศูนย์"

ข้อผิดพลาดนี้เกิดขึ้นเมื่อเธรด A ถูกจองไว้หลังคำสั่ง if เท่านั้นซึ่งหายากมาก แต่สามารถเกิดขึ้นได้


21

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

ตามวิกิพีเดีย :

คำนี้มีจุดกำเนิดมาจากความคิดที่ว่าสัญญาณสองตัวแข่งกันเพื่อ มีอิทธิพลต่อเอาต์พุตก่อนมีผลต่อการส่งออกครั้งแรก

สภาพการแข่งขันในวงจรตรรกะ:

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

อุตสาหกรรมซอฟต์แวร์ใช้คำนี้โดยไม่มีการดัดแปลงซึ่งทำให้เข้าใจยากเล็กน้อย

คุณต้องทำการแทนที่เพื่อแมปกับโลกของซอฟต์แวร์:

  • "สองสัญญาณ" => "สองกระทู้" / "สองกระบวนการ"
  • "มีอิทธิพลต่อเอาต์พุต" => "มีผลกับบางสถานะที่ใช้ร่วมกัน"

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


20

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


คำอธิบายที่ยอดเยี่ยมเพียง
gokareless

สถานะสุดท้ายของอะไร
Roman Alexandrovich

1
@RomanAlexandrovich สถานะสุดท้ายของโปรแกรม รัฐหมายถึงสิ่งต่าง ๆ เช่นค่าของตัวแปร ฯลฯ ดูคำตอบที่ดีเลิศของ Lehane "สถานะ" ในตัวอย่างของเขาจะอ้างถึงค่าสุดท้ายของ 'x' และ 'y'
AMTerp

19

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

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

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

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


10

Microsoft ได้ตีพิมพ์บทความที่มีรายละเอียดเกี่ยวกับสภาพการแข่งขันและการหยุดชะงักของจริง สรุปโดยย่อจากมันจะเป็นย่อหน้าชื่อ:

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


5

สภาพการแข่งขันคืออะไร?

สถานการณ์เมื่อกระบวนการขึ้นอยู่กับช่วงเวลาหรือเหตุการณ์อื่น ๆ

ตัวอย่างเช่นโปรเซสเซอร์ A และโปรเซสเซอร์ B ต้องการทรัพยากรที่เหมือนกันสำหรับการดำเนินการ

คุณตรวจจับพวกมันได้อย่างไร

มีเครื่องมือในการตรวจสอบสภาพการแข่งขันโดยอัตโนมัติ:

คุณจัดการกับมันอย่างไร

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

คุณจะป้องกันไม่ให้เกิดขึ้นได้อย่างไร

มีหลายวิธีที่จะป้องกันไม่ให้สภาพการแข่งขันเช่นนี้เป็นส่วนสำคัญหลีกเลี่ยง

  1. ไม่มีสองกระบวนการพร้อมกันภายในภูมิภาคที่สำคัญของพวกเขา (ไม่รวมร่วมกัน)
  2. ไม่มีการสันนิษฐานเกี่ยวกับความเร็วหรือจำนวนของ CPU
  3. ไม่มีกระบวนการทำงานนอกภูมิภาคที่สำคัญซึ่งบล็อกกระบวนการอื่น ๆ
  4. ไม่มีกระบวนการใดต้องรอตลอดไปเพื่อเข้าสู่ภูมิภาคที่สำคัญ (A กำลังรอทรัพยากร B, B กำลังรอทรัพยากร C, C กำลังรอทรัพยากร A)

2

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

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


2

นี่คือตัวอย่างยอดดุลบัญชีธนาคารคลาสสิกที่จะช่วยให้มือใหม่ทำความเข้าใจกับเธรดใน Java ได้อย่างง่ายดาย wrt เงื่อนไขการแข่งขัน:

public class BankAccount {

/**
 * @param args
 */
int accountNumber;
double accountBalance;

public synchronized boolean Deposit(double amount){
    double newAccountBalance=0;
    if(amount<=0){
        return false;
    }
    else {
        newAccountBalance = accountBalance+amount;
        accountBalance=newAccountBalance;
        return true;
    }

}
public synchronized boolean Withdraw(double amount){
    double newAccountBalance=0;
    if(amount>accountBalance){
        return false;
    }
    else{
        newAccountBalance = accountBalance-amount;
        accountBalance=newAccountBalance;
        return true;
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    BankAccount b = new BankAccount();
    b.accountBalance=2000;
    System.out.println(b.Withdraw(3000));

}

1

คุณสามารถป้องกันสภาพการแข่งขันหากคุณใช้คลาส "Atomic" เหตุผลเป็นเพียงด้ายไม่แยกการดำเนินการรับและตั้งค่าตัวอย่างด้านล่าง:

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

ดังนั้นคุณจะมี 7 ในลิงก์ "ai" แม้ว่าคุณจะทำสองการกระทำ แต่การดำเนินการทั้งสองยืนยันเธรดเดียวกันและไม่มีเธรดอื่นใดที่จะแทรกแซงสิ่งนี้นั่นหมายความว่าไม่มีเงื่อนไขการแข่งขัน!


0

ลองตัวอย่างพื้นฐานนี้เพื่อทำความเข้าใจสภาพการแข่งขันให้ดีขึ้น:

    public class ThreadRaceCondition {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Account myAccount = new Account(22222222);

        // Expected deposit: 250
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.DEPOSIT, 5.00);
            t.start();
        }

        // Expected withdrawal: 50
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.WITHDRAW, 1.00);
            t.start();

        }

        // Temporary sleep to ensure all threads are completed. Don't use in
        // realworld :-)
        Thread.sleep(1000);
        // Expected account balance is 200
        System.out.println("Final Account Balance: "
                + myAccount.getAccountBalance());

    }

}

class Transaction extends Thread {

    public static enum TransactionType {
        DEPOSIT(1), WITHDRAW(2);

        private int value;

        private TransactionType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    };

    private TransactionType transactionType;
    private Account account;
    private double amount;

    /*
     * If transactionType == 1, deposit else if transactionType == 2 withdraw
     */
    public Transaction(Account account, TransactionType transactionType,
            double amount) {
        this.transactionType = transactionType;
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        switch (this.transactionType) {
        case DEPOSIT:
            deposit();
            printBalance();
            break;
        case WITHDRAW:
            withdraw();
            printBalance();
            break;
        default:
            System.out.println("NOT A VALID TRANSACTION");
        }
        ;
    }

    public void deposit() {
        this.account.deposit(this.amount);
    }

    public void withdraw() {
        this.account.withdraw(amount);
    }

    public void printBalance() {
        System.out.println(Thread.currentThread().getName()
                + " : TransactionType: " + this.transactionType + ", Amount: "
                + this.amount);
        System.out.println("Account Balance: "
                + this.account.getAccountBalance());
    }
}

class Account {
    private int accountNumber;
    private double accountBalance;

    public int getAccountNumber() {
        return accountNumber;
    }

    public double getAccountBalance() {
        return accountBalance;
    }

    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean deposit(double amount) {
        if (amount < 0) {
            return false;
        } else {
            accountBalance = accountBalance + amount;
            return true;
        }
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean withdraw(double amount) {
        if (amount > accountBalance) {
            return false;
        } else {
            accountBalance = accountBalance - amount;
            return true;
        }
    }
}

0

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

อย่างไรก็ตามการใช้เครื่องมือสำหรับตรวจจับสภาพการแข่งขันจะถูกตรวจพบว่าเป็นสภาพการแข่งขันที่เป็นอันตราย

รายละเอียดเพิ่มเติมเกี่ยวกับสภาพการแข่งขันที่นี่http://msdn.microsoft.com/en-us/magazine/cc546569.aspx


คำตอบของคุณใช้ภาษาอะไร
MikeMB

ตรงไปตรงมาดูเหมือนว่าฉันว่าถ้าคุณมีสภาพการแข่งขันต่อ seคุณจะไม่ได้ architecting รหัสของคุณในลักษณะแน่นควบคุม ซึ่งแม้ว่ามันจะไม่เป็นปัญหาในกรณีของคุณ แต่เป็นหลักฐานของปัญหาที่ใหญ่กว่าด้วยวิธีที่คุณออกแบบและพัฒนาซอฟต์แวร์ คาดว่าจะเผชิญกับข้อบกพร่องสภาพการแข่งขันที่เจ็บปวดไม่ช้าก็เร็ว
วิศวกร

0

พิจารณาการดำเนินการที่ต้องแสดงการนับทันทีที่จำนวนเพิ่มขึ้น ie. เร็วที่สุดเท่าที่CounterThreadเพิ่มค่าDisplayThreadต้องการที่จะแสดงค่าปรับปรุงเมื่อเร็ว ๆ

int i = 0;

เอาท์พุต

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

ที่นี่CounterThreadได้รับการล็อคบ่อยครั้งและอัพเดทค่าก่อนที่DisplayThreadจะแสดงมัน นี่คือสภาพการแข่งขัน สภาพการแข่งขันสามารถแก้ไขได้โดยใช้ Synchronzation


0

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

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