ทำไม pthread_cond_wait ถึงมีการปลุกที่ผิดพลาด?


145

ในการอ้างถึงหน้าคน:

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

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

ทำไมไม่pthread_cond_waitกลับ spuriously? ทำไมถึงไม่สามารถรับประกันได้ว่าจะมีการปลุกเมื่อมีการส่งสัญญาณอย่างถูกต้องเท่านั้น ทุกคนสามารถอธิบายเหตุผลของพฤติกรรมหลอกลวงได้หรือไม่


5
ฉันคิดว่ามันมีบางอย่างที่เกี่ยวข้องกับการส่งคืนเมื่อใดก็ตามที่กระบวนการจับสัญญาณ * nixes ส่วนใหญ่จะไม่รีสตาร์ทการบล็อคการโทรหลังจากสัญญาณขัดจังหวะ พวกเขาเพียงแค่ตั้งค่า / ส่งคืนรหัสข้อผิดพลาดที่แจ้งว่าเกิดสัญญาณ
cHao

1
@cHao: แม้ว่าจะทราบว่าเนื่องจากตัวแปรเงื่อนไขมีเหตุผลอื่น ๆสำหรับการปลุกแบบปลอมการจัดการสัญญาณไม่ใช่ข้อผิดพลาดสำหรับpthread_cond_(timed)wait: "หากมีการส่งสัญญาณ ... เธรดจะดำเนินการต่อเพื่อรอตัวแปรเงื่อนไขราวกับว่ามันเป็น ไม่ขัดจังหวะหรือมันจะกลับเป็นศูนย์เนื่องจากการปลุกปลอม " ฟังก์ชั่นการปิดกั้นอื่น ๆ ระบุEINTRเมื่อถูกขัดจังหวะด้วยสัญญาณ (เช่นread) หรือจำเป็นต้องกลับมาทำงาน (เช่นpthread_mutex_lock) ดังนั้นหากไม่มีเหตุผลอื่นใดที่ทำให้ตื่นขึ้นมาอย่างpthread_cond_waitไม่น่าเชื่ออาจถูกนิยามเหมือนอย่างใดอย่างหนึ่ง
Steve Jessop

4
บทความที่เกี่ยวข้องกับ Wikipedia: Spurious wakeup
Palec

3
ประโยชน์Vladimir Prus: เก๊ wakeups
iammilind

หลายฟังก์ชั่นไม่สามารถทำงานได้อย่างสมบูรณ์ (ขัดจังหวะ I / O) และฟังก์ชั่นการสังเกตสามารถรับเหตุการณ์ที่ไม่เหมือนการเปลี่ยนแปลงไปยังไดเรกทอรีที่การเปลี่ยนแปลงถูกยกเลิกหรือเปลี่ยนกลับ มีปัญหาอะไร?
curiousguy

คำตอบ:


77

คำอธิบายต่อไปนี้ให้โดย David R. Butenhof ใน"การเขียนโปรแกรมด้วย POSIX หัวข้อ" (หน้า 80):

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

ในการอภิปราย comp.programming.threadsต่อไปนี้เขาขยายความคิดที่อยู่เบื้องหลังการออกแบบ:

Patrick Doyle เขียนว่า: 
> ในบทความ Tom Payne เขียนว่า: 
>> Kaz Kylheku wrote: 
>>: เนื่องจากการใช้งานบางครั้งไม่สามารถหลีกเลี่ยงการแทรกได้ 
>>: Wakeups ปลอมเหล่านี้ อาจมีค่าใช้จ่ายสูงในการป้องกัน

>> แต่ทำไม ทำไมเรื่องนี้ถึงยากนัก ตัวอย่างเช่นเรากำลังพูดถึง
>> สถานการณ์ที่การรอคอยหมดเวลาเมื่อมีสัญญาณเข้ามา? 

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

> พวกเขาอาจไม่มีการนำไปปฏิบัติโดยเฉพาะในใจ 

จริงๆแล้วคุณอยู่ไม่ไกลเลยยกเว้นว่าคุณไม่ได้ผลักมันมากพอ 

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

เราทำตามความตั้งใจนั้นพร้อมด้วยเหตุผลหลายระดับ อย่างแรกก็คือ
"เคร่งครัด" โดยใช้การวนซ้ำช่วยปกป้องแอปพลิเคชันจากความไม่สมบูรณ์ของตัวเอง 
การปฏิบัติที่เข้ารหัส อย่างที่สองก็คือมันไม่ยากที่จะจินตนาการอย่างเป็นนามธรรม
เครื่องจักรและรหัสการใช้งานที่สามารถใช้ประโยชน์จากข้อกำหนดนี้เพื่อปรับปรุง 
ประสิทธิภาพของการดำเนินการตามเงื่อนไขเฉลี่ยรอการปรับให้เหมาะสม 
กลไกการซิงโครไนซ์ 
/ ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
| Compaq Computer Corporation POSIX สถาปนิกสถาปนิก |
| หนังสือของฉัน: http://www.awl.com/cseng/titles/0-201-63392-2/ |
\ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 


22
โดยพื้นฐานแล้วไม่มีอะไรจะพูด ไม่มีคำอธิบายใด ๆ นอกเหนือจากความคิดเริ่มต้นที่ว่า "มันอาจทำให้เร็วขึ้น" แต่ก็ไม่มีใครรู้ว่ามันจะทำได้หรือไม่
Bogdan Ionitza

107

อย่างน้อยสองสิ่งที่ 'การปลุกให้ดีลวงตา' อาจหมายถึง:

  • เธรดที่ถูกบล็อกในpthread_cond_waitสามารถส่งคืนจากการโทรแม้ว่าจะไม่มีการเรียกไปยังpthread_call_signalหรือpthread_cond_broadcastบนเงื่อนไข
  • เธรดที่ถูกบล็อกด้วยการpthread_cond_waitส่งคืนเนื่องจากการเรียกไปยังpthread_cond_signalหรือpthread_cond_broadcastอย่างไรก็ตามหลังจากการเรียกใช้ mutex อีกครั้งพบว่าเพรดิเคตพื้นฐานไม่เป็นจริงอีกต่อไป

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

  • เธรด 1 เพิ่ง dequeued องค์ประกอบและปล่อย mutex และตอนนี้คิวว่างเปล่า เธรดกำลังทำสิ่งที่มันทำกับองค์ประกอบที่ได้มาบน CPU บางตัว
  • เธรด 2 พยายามที่จะถอนออกจากองค์ประกอบ แต่พบว่าคิวนั้นว่างเปล่าเมื่อตรวจสอบภายใต้ mutex การโทรpthread_cond_waitและบล็อกในการโทรที่รอสัญญาณ / การออกอากาศ
  • เธรด 3 รับ mutex แทรกอิลิเมนต์ใหม่เข้าในคิวแจ้งตัวแปรเงื่อนไขและปลดล็อก
  • ในการตอบสนองต่อการแจ้งเตือนจากเธรด 3 เธรด 2 ซึ่งรออยู่บนเงื่อนไขถูกกำหนดเวลาให้รัน
  • อย่างไรก็ตามก่อนที่เธรด 2 จะจัดการกับ CPU และคว้าการล็อกคิวเธรด 1 จะเสร็จสิ้นภารกิจปัจจุบันและกลับไปที่คิวเพื่อทำงานมากขึ้น มันได้รับการล็อคคิวตรวจสอบภาคและพบว่ามีการทำงานในคิว มันดำเนินการเพื่อถอนคิวไอเท็มที่เธรด 3 แทรกปลดล็อกและทำสิ่งที่มันทำกับไอเท็มที่เธรด 3 เข้าคิว
  • ตอนนี้เธรด 2 ขึ้นบน CPU และรับการล็อก แต่เมื่อตรวจสอบเพรดิเคตพบว่าคิวนั้นว่างเปล่า หัวข้อที่ 1 'ขโมย' รายการดังนั้นการปลุกจะดูเหมือนเป็นของปลอม เธรด 2 จำเป็นต้องรอเงื่อนไขอีกครั้ง

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


23
ใช่. นี่เป็นสิ่งที่เกิดขึ้นเมื่อมีการใช้เหตุการณ์แทนกลไกการซิงโครไนซ์ที่มีการนับ น่าเศร้าที่ดูเหมือนว่า POSIX semaphores (บน Linux อยู่แล้ว) อาจมีการปลุกด้วย spurius ด้วยเช่นกัน ฉันเพิ่งพบว่าแปลกเล็กน้อยที่ความสามารถในการทำงานขั้นพื้นฐานของการซิงโครไนซ์แบบดั้งเดิมนั้นได้รับการยอมรับว่าเป็น 'ปกติ' และจะต้องแก้ไขในระดับผู้ใช้ :( สันนิษฐานได้ว่านักพัฒนาจะมีความทันสมัยมากขึ้น ด้วยส่วน 'ปลอมลวงตา' หรืออาจเป็น 'ปลอมแปลงเชื่อมต่อกับ URL ที่ไม่ถูกต้อง' หรือ 'เปิดไฟล์ผิดไฟล์ปลอม'
Martin James

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

1
@MartinJames - EINTR "ปลอม" แบบคลาสสิกเป็นอย่างไร? ฉันจะยอมรับว่าการทดสอบอย่างต่อเนื่องสำหรับ EINTR ในลูปนั้นน่ารำคาญและทำให้โค้ดค่อนข้างน่าเกลียด
CubicleSoft

2
@Yola ไม่มันไม่สามารถเพราะคุณควรจะล็อค mutex รอบได้pthread_cond_signal/broadcastและคุณจะไม่สามารถที่จะทำเช่นนั้นจนกว่า mutex pthread_cond_waitจะถูกปลดล็อกโดยการเรียก
a3f

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

7

ส่วน "การกระตุ้นหลายครั้งโดยสัญญาณสภาพ" ในpthread_cond_signalมีตัวอย่างการนำไปปฏิบัติของ pthread_cond_wait และ pthread_cond_signal ซึ่งเกี่ยวข้องกับการปลุกปลอม


2
ฉันคิดว่าคำตอบนี้ผิดไปเท่าที่มันจะไป ตัวอย่างการใช้งานในหน้านั้นมีการใช้งาน "แจ้งเตือนหนึ่ง" ซึ่งเทียบเท่ากับ "แจ้งเตือนทั้งหมด"; แต่ดูเหมือนว่าจะไม่ทำให้เกิดการปลุกจริงๆ วิธีเดียวสำหรับเธรดที่จะปลุกคือการใช้เธรดอื่นที่เรียกว่า "แจ้งเตือนทั้งหมด" หรือโดยเธรดอื่น ๆ ที่เรียกใช้สิ่งที่มีป้ายกำกับ - "แจ้งให้ทราบ" - แจ้งเตือนหนึ่ง "- ซึ่ง - เป็นจริง -" แจ้งเตือนทั้งหมด "
Quuxplusone

5

ในขณะที่ฉันไม่คิดว่ามันได้รับการพิจารณาในขณะออกแบบนี่คือเหตุผลทางเทคนิคจริง: เมื่อใช้ร่วมกับการยกเลิกเธรดมีเงื่อนไขที่ใช้ตัวเลือกในการปลุก "เก๊บ" อาจมีความจำเป็นอย่างน้อยเว้นแต่คุณ ยินดีที่จะกำหนดข้อ จำกัด ที่แข็งแกร่งมาก ๆ เกี่ยวกับกลยุทธ์การดำเนินการที่เป็นไปได้

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

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

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