“ ความผันผวน” รับประกันอะไรในรหัส C แบบพกพาสำหรับระบบมัลติคอร์หรือไม่?


12

หลังจากที่กำลังมองหาที่พวง ของ อื่น ๆ คำถาม และ พวกเขา ตอบฉันได้รับความประทับใจที่ไม่มีข้อตกลงอย่างกว้างขวางในสิ่งที่ "ผันผวน" คำหลักใน C หมายความว่า

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

ท่ามกลางปัญหาอื่น ๆ :

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

เพื่อสรุปปัญหาปรากฏขึ้น (หลังจากอ่านมาก) ว่า "volatile" รับประกันสิ่งที่ต้องการ: ค่าจะอ่าน / เขียนไม่เพียงจาก / ไปยังการลงทะเบียน แต่อย่างน้อยกับแคช L1 หลักในลำดับเดียวกันกับที่ การอ่าน / เขียนจะปรากฏขึ้นในรหัส แต่สิ่งนี้ดูไร้ประโยชน์เนื่องจากการอ่าน / เขียนจาก / ไปยังการลงทะเบียนนั้นเพียงพอแล้วในเธรดเดียวกันในขณะที่การประสานงานกับแคช L1 ไม่รับประกันอะไรเพิ่มเติมเกี่ยวกับการประสานงานกับเธรดอื่น ๆ ฉันนึกภาพไม่ออกเลยว่ามันจะสำคัญเมื่อต้องซิงค์เพียงกับแคช L1

ใช้ 1 การใช้
งานเพียงอย่างเดียวที่ยอมรับได้อย่างกว้างขวางสำหรับระบบเก่าหรือระบบฝังตัวที่ตำแหน่งหน่วยความจำบางตำแหน่งถูกแมปฮาร์ดแวร์กับฟังก์ชั่น I / O เช่นหน่วยความจำเล็กน้อยที่ควบคุม (ในฮาร์ดแวร์โดยตรง) แสง หรือหน่วยความจำเล็ก ๆ น้อย ๆ ที่บอกคุณว่าแป้นคีย์บอร์ดหยุดทำงานหรือไม่ (เพราะฮาร์ดแวร์เชื่อมต่อโดยตรงกับกุญแจ)

ดูเหมือนว่า"ใช้ 1" ไม่เกิดขึ้นในรหัสพกพาที่มีเป้าหมายรวมถึงระบบมัลติคอร์

ใช้ 2
ไม่แตกต่างจาก "ใช้ 1" คือหน่วยความจำที่สามารถอ่านหรือเขียนได้ตลอดเวลาโดยตัวจัดการขัดจังหวะ (ซึ่งอาจควบคุมแสงหรือข้อมูลการจัดเก็บจากคีย์) แต่สำหรับตอนนี้เรามีปัญหาที่ขึ้นอยู่กับระบบตัวจัดการขัดจังหวะอาจทำงานบน แกนที่แตกต่างกับหน่วยความจำแคชของตัวเองและ "ระเหย" ไม่รับประกันความสอดคล้องกันแคชในทุกระบบ

ดังนั้น"ใช้ 2" ดูเหมือนจะเกินกว่าที่ "ระเหย" สามารถส่งมอบได้

ใช้ 3
การใช้ที่ไม่มีข้อโต้แย้งเพียงอย่างเดียวที่ฉันเห็นคือการป้องกันการปรับการเข้าถึงผิดพลาดผ่านตัวแปรต่างๆที่ชี้ไปยังหน่วยความจำเดียวกันที่คอมไพเลอร์ไม่ได้ตระหนักถึงคือหน่วยความจำเดียวกัน แต่สิ่งนี้อาจไม่มีข้อโต้แย้งเพียงเพราะคนไม่ได้พูดถึงมัน - ฉันเห็นเพียงหนึ่งกล่าวถึง และฉันคิดว่ามาตรฐาน C รู้อยู่แล้วว่าพอยน์เตอร์ "แตกต่าง" (เช่น args ที่แตกต่างกันไปยังฟังก์ชั่น) อาจชี้ไปที่รายการเดียวกันหรือรายการใกล้เคียงและระบุไว้แล้วว่าคอมไพเลอร์จะต้องสร้างรหัส อย่างไรก็ตามฉันไม่พบหัวข้อนี้อย่างรวดเร็วในมาตรฐาน (500 หน้า!)

ดังนั้น"ใช้ 3" อาจไม่มีอยู่เลย?

ดังนั้นคำถามของฉัน:

"volatile" รับประกันอะไรในรหัส C แบบพกพาสำหรับระบบมัลติคอร์หรือไม่?


แก้ไข - อัปเดต

หลังจากเรียกดูมาตรฐานล่าสุดก็จะมองเช่นคำตอบคืออย่างน้อยมากจำกัด ใช่:
1. มาตรฐานซ้ำ ๆ ระบุการรักษาพิเศษสำหรับประเภทที่เฉพาะเจาะจง "sig_atomic_t ผันผวน" อย่างไรก็ตามมาตรฐานยังบอกว่าการใช้ฟังก์ชั่นสัญญาณในโปรแกรมแบบมัลติเธรดส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด ดังนั้นกรณีใช้งานนี้ดูเหมือนจะ จำกัด การสื่อสารระหว่างโปรแกรมแบบเธรดเดียวและตัวจัดการสัญญาณ
2. มาตรฐานยังระบุความหมายที่ชัดเจนสำหรับ "volatile" ที่สัมพันธ์กับ setjmp / longjmp (ตัวอย่างรหัสที่ให้ไว้ในคำถามและคำตอบอื่น ๆ)

ดังนั้นคำถามที่แม่นยำยิ่งขึ้นจะกลายเป็น:
"ระเหย" รับประกันอะไรในรหัส C แบบพกพาสำหรับระบบมัลติคอร์นอกเหนือจาก (1) อนุญาตให้โปรแกรมแบบเธรดเดียวรับข้อมูลจากตัวจัดการสัญญาณหรือ (2) อนุญาตให้ setjmp รหัสเพื่อดูตัวแปรแก้ไขระหว่าง setjmp และ longjmp?

นี่ยังเป็นคำถามใช่ / ไม่ใช่

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


3
สัญญาณมีอยู่ในพกพา C; สิ่งที่เกี่ยวกับตัวแปรทั่วโลกที่มีการปรับปรุงโดยตัวจัดการสัญญาณ? สิ่งนี้จะต้องvolatileแจ้งโปรแกรมว่าอาจเปลี่ยนแปลงแบบอะซิงโครนัส
Nate Eldredge

2
@NateEldredge Global ในขณะที่ความผันผวนเพียงอย่างเดียวนั้นไม่ดีพอ มันต้องเป็นอะตอมเช่นกัน
ยูจีน Sh.

@EugeneSh .: ใช่แน่นอน แต่คำถามในมือเป็นเรื่องเกี่ยวกับvolatileโดยเฉพาะซึ่งฉันเชื่อว่าจำเป็น
Nate Eldredge

" ในขณะที่การประสานงานกับแคช L1 ไม่รับประกันอะไรเพิ่มเติมเกี่ยวกับการประสานงานกับเธรดอื่น ๆ " ที่ไหน "การประสานงานกับแคช L1" ไม่เพียงพอที่จะสื่อสารกับเธรดอื่น ๆ
curiousguy

1
อาจเกี่ยวข้อง, ข้อเสนอ C ++ เพื่อลดความผันผวน , ข้อเสนอตอบข้อกังวลมากมายที่คุณกล่าวถึงที่นี่, และบางทีผลลัพธ์อาจมีอิทธิพลต่อคณะกรรมการ C
MM

คำตอบ:


1

เพื่อสรุปปัญหาปรากฏขึ้น (หลังจากอ่านมาก) ว่า "volatile" รับประกันสิ่งที่ต้องการ: ค่าจะอ่าน / เขียนไม่เพียงจาก / ไปยังการลงทะเบียน แต่อย่างน้อยกับแคช L1 หลักในลำดับเดียวกันกับที่ อ่าน / เขียนปรากฏในรหัส

ไม่มันอย่างแน่นอนไม่ได้ และนั่นทำให้ความผันผวนเกือบไร้ประโยชน์สำหรับจุดประสงค์ของรหัสความปลอดภัย MT

ถ้าเป็นเช่นนั้นความผันผวนจะค่อนข้างดีสำหรับตัวแปรที่แชร์หลายเธรดเนื่องจากการสั่งซื้อเหตุการณ์ในแคช L1 นั้นเป็นสิ่งที่คุณต้องทำใน CPU ทั่วไป (นั่นคือมัลติคอร์หรือมัลติซีพียูบนเมนบอร์ด) ที่สามารถร่วมมือกันได้ ในวิธีที่ทำให้การใช้งานปกติของ C / C ++ หรือ Java multithreading เป็นไปได้ด้วยค่าใช้จ่ายที่คาดหวังโดยทั่วไป (นั่นคือไม่ใช่ค่าใช้จ่ายจำนวนมากสำหรับการดำเนินการ mutex แบบปรมาณูหรือไม่มีเนื้อหา)

แต่สารระเหยไม่ได้ให้การสั่งซื้อใด ๆ ที่รับประกัน (หรือ "การมองเห็นของหน่วยความจำ") ในแคชไม่ว่าในทางทฤษฎีหรือในทางปฏิบัติ

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

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

  • คุณอาจใช้ptrace(กลไกคล้าย ptrace) เพื่อกำหนดจุดพักที่มีความหมายที่จุดลำดับหลังจากการดำเนินการที่เกี่ยวข้องกับวัตถุที่ระเหยได้: คุณสามารถทำลายที่จุดเหล่านี้ได้อย่างแท้จริง (โปรดทราบว่าสิ่งนี้ใช้ได้เฉพาะในกรณีที่คุณต้องการตั้งจุดพักหลายจุด คำสั่ง C / C ++ อาจถูกคอมไพล์ไปยังจุดเริ่มต้นและจุดสิ้นสุดการชุมนุมที่แตกต่างกันมากมายเช่นในวงวนที่ยังไม่ผ่านการควบคุมขนาดใหญ่)
  • ในขณะที่เธรดของการดำเนินการหยุดคุณอาจอ่านค่าของวัตถุที่ระเหยได้ทั้งหมดเนื่องจากมีการแสดงที่เป็นที่ยอมรับ (ตาม ABI ตามประเภท) ตัวแปรโลคัลที่ไม่ลบเลือนอาจมีการแทนค่าผิดปกติ f.ex การเป็นตัวแทนแบบเลื่อน: ตัวแปรที่ใช้สำหรับการทำดัชนีอาเรย์อาจถูกคูณด้วยขนาดของวัตถุแต่ละชิ้นเพื่อให้การจัดทำดัชนีง่ายขึ้น หรืออาจถูกแทนที่ด้วยตัวชี้ไปยังองค์ประกอบอาร์เรย์ (ตราบใดที่การใช้งานของตัวแปรทั้งหมดถูกแปลงในทำนองเดียวกัน) (คิดว่าการเปลี่ยน dx เป็น du ในอินทิกรัล)
  • คุณยังสามารถแก้ไขวัตถุเหล่านั้นได้ (ตราบใดที่การแม็ปหน่วยความจำอนุญาตนั้นเนื่องจากวัตถุที่ระเหยได้ซึ่งมีอายุการใช้งานคงที่ซึ่งคุณสมบัติที่ผ่านการรับรองอาจอยู่ในช่วงหน่วยความจำที่แม็พแบบอ่านอย่างเดียว)

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

โปรดทราบว่าความสามารถในการดีบักโปรแกรมแบบเต็มซึ่งกำลังพิจารณาตัวแปรทั้งหมดอย่างน้อยที่จุดลำดับจัดทำโดยโหมด "zero optimization" ของคอมไพเลอร์ซึ่งเป็นโหมดที่ยังคงทำการปรับให้เหมาะสมเล็กน้อยเช่นการทำให้ง่ายขึ้นทางคณิตศาสตร์ การปรับให้เหมาะสมที่สุดในทุกโหมด) แต่ความผันผวนนั้นแข็งแกร่งกว่าการปรับให้เหมาะสมที่สุด: x-xสามารถทำให้ง่ายขึ้นสำหรับจำนวนเต็มที่ไม่ระเหยxแต่ไม่ใช่ของวัตถุที่ระเหยได้

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

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

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

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

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

กรณีทั่วไปคือสิ่งที่คอมไพเลอร์ทำเมื่อไม่มีข้อมูลใด ๆ เกี่ยวกับโครงสร้าง: f.ex เรียกฟังก์ชั่นเสมือนจริงบน lvalue ผ่านการจัดส่งแบบไดนามิกเป็นกรณีทั่วไปทำให้การเรียกโดยตรงไปยังผู้เอาชนะหลังจากการพิจารณาในเวลารวบรวมประเภทของวัตถุที่กำหนดโดยการแสดงออกเป็นกรณีเฉพาะ คอมไพเลอร์มีการจัดการเคสทั่วไปของโครงสร้างทั้งหมดเสมอและเป็นไปตาม ABI

ความผันผวนไม่ทำอะไรเป็นพิเศษในการซิงโครไนซ์เธรดหรือให้ "การมองเห็นของหน่วยความจำ": การระเหยเพียงให้การรับประกันในระดับนามธรรมที่เห็นจากภายในเธรดที่ดำเนินการหรือหยุดนั่นคือภายใน CPU core :

  • volatile ไม่ได้เกี่ยวกับการดำเนินการของหน่วยความจำถึง RAM หลัก (คุณอาจตั้งค่าประเภทแคชหน่วยความจำเฉพาะพร้อมคำแนะนำการประกอบหรือการเรียกระบบเพื่อรับการรับประกันเหล่านี้);
  • ระเหยไม่ได้ให้การรับประกันใด ๆ เกี่ยวกับการดำเนินงานเมื่อหน่วยความจำจะมุ่งมั่นที่จะระดับของแคชใด ๆ (ไม่ได้ L1)

เฉพาะจุดที่สองหมายความว่าการระเหยนั้นไม่มีประโยชน์ในปัญหาการสื่อสารระหว่างเธรดส่วนใหญ่ ประเด็นแรกนั้นไม่เกี่ยวข้องกับปัญหาการเขียนโปรแกรมใด ๆ ที่ไม่เกี่ยวข้องกับการสื่อสารกับส่วนประกอบฮาร์ดแวร์ภายนอกซีพียู แต่ยังคงอยู่บนบัสหน่วยความจำ

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

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


6

ฉันไม่มีความเชี่ยวชาญ แต่ cppreference.com มีสิ่งที่ปรากฏให้ฉันจะเป็นบางส่วนที่ดีงามข้อมูลเกี่ยวกับ volatileนี่คือส่วนสำคัญของมัน:

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

มันยังให้ประโยชน์บางอย่าง:

การใช้งานของสารระเหย

1) วัตถุระเหยแบบคงที่พอร์ต I / O ที่ถูกแมปหน่วยความจำแบบจำลองและวัตถุคงที่แบบระเหยวัตถุแบบจำลองพอร์ตอินพุตหน่วยความจำที่แมปเช่นนาฬิกาเรียลไทม์

2) วัตถุระเหยแบบคงที่ประเภท sig_atomic_t ใช้สำหรับการสื่อสารกับตัวจัดการสัญญาณ

3) ตัวแปรระเหยที่เป็นแบบโลคอลสำหรับฟังก์ชั่นที่มีการร้องขอของแมโคร setjmp เป็นตัวแปรโลคอลเดียวที่รับประกันว่าจะรักษาค่าของมันหลังจากที่ longjmp ส่งคืน

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

และแน่นอนว่ามันvolatileไม่มีประโยชน์สำหรับการซิงโครไนซ์เธรด:

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


2
โดยเฉพาะอย่างยิ่ง (2) และ (3) เกี่ยวข้องกับรหัสพกพา
Nate Eldredge

2
@TED ​​แม้จะมีชื่อโดเมนลิงก์คือข้อมูลเกี่ยวกับ C ไม่ใช่ C ++
David Brown

@NateEldredge คุณไม่ค่อยสามารถใช้longjmpในรหัส C ++ ได้
curiousguy

@DavidBrown C และ C ++ มีความหมายเดียวกันของ SE ที่สังเกตได้และเป็นหลักดั้งเดิมเธรดเดียวกัน
curiousguy

4

ก่อนอื่นมีอาการสะอึกต่าง ๆ ที่เกี่ยวกับการตีความที่แตกต่างกันของความหมายของvolatileการเข้าถึงและสิ่งที่คล้ายกัน ดูการศึกษาครั้งนี้: Volatiles Are Miscompiled และจะทำอย่างไรกับมัน

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

ไม่ว่าภาษา C จะรับประกันพฤติกรรมของหน่วยความจำหรือไม่volatileก็ตามสามารถพิสูจน์ได้ แต่โดยส่วนตัวแล้วฉันคิดว่าภาษานั้นชัดเจน ก่อนอื่นเรามีคำจำกัดความที่เป็นทางการของผลข้างเคียง, C17 5.1.2.3:

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

มาตรฐานกำหนดคำว่า sequencing ซึ่งเป็นวิธีการกำหนดลำดับของการประเมินผล (การดำเนินการ) คำจำกัดความเป็นทางการและยุ่งยาก:

Sequenced beforeคือความสัมพันธ์แบบไม่สมมาตรสกรรมกริยาคู่ที่ชาญฉลาดระหว่างการประเมินที่ดำเนินการโดยเธรดเดี่ยว รับการประเมินสอง A และ B ถ้า A ถูกลำดับก่อน B การดำเนินการของ A จะนำหน้าการดำเนินการของ B (ตรงกันข้ามถ้า A ถูกลำดับก่อน B แล้ว B จะถูก จัดลำดับหลังจาก A) หาก A ไม่ได้ถูกจัดลำดับ ก่อนหรือหลัง B แล้ว A และ B มี unsequenced การประเมิน A และ B จะถูกจัดลำดับโดยไม่ทราบแน่ชัดเมื่อ A ถูกจัดลำดับทั้งก่อนหรือหลัง B แต่จะไม่ระบุว่าจะให้อะไร 13) การปรากฏตัวของจุดลำดับ ระหว่างการประเมินผลของนิพจน์ A และ B บอกเป็นนัยว่าการคำนวณค่าทุกอย่างและผลข้างเคียงที่เกี่ยวข้องกับ A นั้นถูกจัดลำดับไว้ก่อนการคำนวณค่าทุกครั้งและผลข้างเคียงที่เกี่ยวข้องกับ B. (บทสรุปของคะแนนลำดับได้รับในภาคผนวก C. )

สินค้า TL; DR ดังกล่าวข้างต้นเป็นพื้นว่าในกรณีที่เรามีการแสดงออกAที่มีผลข้างเคียงก็จะต้องทำก่อนที่จะดำเนินการแสดงออกอื่นBในกรณีที่เป็นลำดับขั้นตอนหลังจากBA

การเพิ่มประสิทธิภาพของรหัส C สามารถทำได้ผ่านส่วนนี้:

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

ซึ่งหมายความว่าโปรแกรมอาจประเมิน (เรียกใช้) นิพจน์ตามลำดับมาตรฐานที่อื่น (ลำดับของการประเมิน ฯลฯ ) แต่ไม่จำเป็นต้องประเมินค่า (ดำเนินการ) ค่าถ้ามันสามารถอนุมานได้ว่ามันไม่ได้ใช้ ยกตัวอย่างเช่นการดำเนินการที่0 * xไม่จำเป็นต้องใช้ในการประเมินและการแสดงออกเพียงแทนที่ด้วยx0

นอกจากว่าการเข้าถึงตัวแปรเป็นผลข้างเคียง หมายความว่าในกรณีxนี้volatileจะต้องประเมิน (ดำเนินการ) 0 * xแม้ว่าผลลัพธ์จะเป็น 0 เสมอการเพิ่มประสิทธิภาพไม่ได้รับอนุญาต

นอกจากนี้มาตรฐานยังพูดถึงพฤติกรรมที่สังเกตได้:

ข้อกำหนดขั้นต่ำในการนำไปปฏิบัติให้สอดคล้องคือ:

  • การเข้าถึงวัตถุระเหยจะถูกประเมินอย่างเคร่งครัดตามกฎของเครื่องนามธรรม
    / - / นี่คือพฤติกรรมที่สังเกตได้ของโปรแกรม

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

ซึ่งหมายความว่าในตัวอย่างนี้

volatile int x;
volatile int y;
z = x;
z = y;

ทั้งสองสำนวนที่ได้รับมอบหมายจะต้องได้รับการประเมินและz = x; จะต้องz = y;ได้รับการประเมินก่อน การใช้งานตัวประมวลผลหลายตัวที่ outsource การดำเนินการทั้งสองนี้ไปยังคอร์ที่ตามลำดับสองที่แตกต่างกันไม่สอดคล้อง

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

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


@currigy ไม่เป็นไร
Lundin

@crownguy ไม่สำคัญตราบใดที่มันเป็นประเภทจำนวนเต็มบางส่วนที่มีหรือไม่มีตัวระบุ
Lundin

หากเป็นจำนวนเต็มแบบไม่ลบเลือนง่ายเหตุใดการเขียนซ้ำซ้อนzจึงถูกเรียกใช้ (เช่นz = x; z = y;) ค่าจะถูกลบในคำสั่งถัดไป
curiousguy

@curiousguy เนื่องจากการอ่านตัวแปรที่ผันผวนจะต้องถูกดำเนินการไม่ว่าในลำดับที่ระบุ
ลุนดิน

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