C ++ std :: memory_order_relaxed ความสับสน

Oct 12 2020

ฉันกำลังอ่านบทความนี้จาก GCC Wiki เกี่ยวกับอุปสรรคของหน่วยความจำ C ++ (และมันยอดเยี่ยมมาก)
ค่อนข้างตรงไปตรงมาจนกระทั่งฉันมาถึงจุดนี้:

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

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

เนื่องจากเธรดไม่จำเป็นต้องซิงโครไนซ์ระหว่างระบบดังนั้นการยืนยันในตัวอย่างนี้อาจ FAIL ได้

โอเคนี่ก็ตรงไปตรงมาเช่นกันลองดำเนินการ ..

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

ผู้ยืนยันไม่สามารถล้มเหลวได้ เมื่อเห็นที่เก็บของ 2 โดยเธรด 2 จะไม่สามารถมองเห็นค่า 1 ได้อีกต่อไปซึ่งจะป้องกันการรวมตัวกันของตัวแปรเดียวในโหลดแบบผ่อนคลายของการอ้างอิงอื่นที่อาจใช้นามแฝง

นี่คือสิ่งที่ทำให้ฉันสับสนทำไม y ไม่สามารถโหลดค่า 2 และ z โหลดค่า 1 ได้ (และทำให้การยืนยันล้มเหลว) เนื่องจากลำดับไม่ซิงโครไนซ์ในเธรด 1

คำตอบ

4 NicolBolas Oct 12 2020 at 12:12

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

แต่กรณีที่สองของคุณแตกต่างกันเนื่องจากมีผลต่อวัตถุอะตอมเดียวกัน

[intro.races] / 10บอกเราว่าภายในเธรดหากการดำเนินการหนึ่งถูกจัดลำดับก่อนอื่นการดำเนินการนั้นจะ "เกิดขึ้นก่อน" อีกอัน และ[intro.races] / 14-17 สรุปพฤติกรรมต่อไปนี้เกี่ยวกับอะตอม :

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

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

การดำเนินการสองรายการของเธรด 1 เรียงลำดับตามความสัมพันธ์ "เกิดขึ้นก่อน" และการดำเนินการของเธรด 2 นั้นได้รับคำสั่งจากความสัมพันธ์ "เกิดขึ้นก่อน"

เนื่องจากทั้งหมดทำหน้าที่ในวัตถุอะตอมเดียวกันหากyได้รับค่าเป็น 2 ดังนั้นจึงต้องxตั้งค่า"เกิดขึ้นหลังจาก" เป็น 2 ดังนั้นลำดับการเข้าถึงxต้องเป็น "x = 1, x = 2, อ่าน x " และเนื่องจากการอ่านครั้งสุดท้ายxเกิดขึ้นหลังจากการอ่านครั้งแรกxค่าที่ได้รับจะต้องไม่เป็น 1