คำอธิบายของการสั่งซื้อที่ผ่อนคลายผิดพลาดใน cppreference หรือไม่?


13

ในเอกสารประกอบของstd::memory_orderบน cppreference.comมีตัวอย่างของการสั่งซื้อแบบผ่อนคลาย:

สั่งผ่อนคลาย

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

ตัวอย่างเช่นเมื่อ x และ y เริ่มต้นที่ศูนย์

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

ได้รับอนุญาตให้สร้าง r1 == r2 == 42 เพราะถึงแม้ว่า A จะถูกจัดลำดับก่อนหน้า B ภายในเธรด 1 และ C ถูกเรียงตามลำดับก่อนหน้า D ภายในเธรด 2 ไม่มีสิ่งใดป้องกันไม่ให้ D ปรากฏก่อน A ในลำดับการปรับเปลี่ยนของ y และ B จาก ปรากฏขึ้นก่อนหน้า C ในลำดับการแก้ไขของ x ผลข้างเคียงของ D ต่อ y สามารถมองเห็นได้จากโหลด A ในเธรด 1 ในขณะที่ผลข้างเคียงของ B บน x สามารถมองเห็นได้ในโหลด C ในเธรด 2 โดยเฉพาะอย่างยิ่งสิ่งนี้อาจเกิดขึ้นหาก D เสร็จสมบูรณ์ก่อน C ใน เธรด 2 อาจเป็นเพราะคอมไพเลอร์สั่งใหม่หรือที่รันไทม์

มันบอกว่า "C เป็นลำดับก่อน D ภายในเธรด 2"

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


คำถามของคุณเกี่ยวกับ C ++ 11 โดยเฉพาะหรือไม่?
curiousguy

ไม่มันใช้กับ c ++ 14,17 ด้วย ฉันรู้ว่าทั้งคอมไพเลอร์และซีพียูอาจจัดลำดับ C ใหม่ด้วย D. แต่ถ้ามีการจัดเรียงใหม่เกิดขึ้น C จะไม่สามารถดำเนินการให้เสร็จสิ้นก่อนที่ D จะเริ่มต้น ดังนั้นฉันคิดว่ามีคำศัพท์ที่ใช้ผิดในประโยค "A เป็นลำดับ - ก่อน B ภายในเธรด 1 และ C เป็นลำดับก่อน D ภายในเธรด 2" มันแม่นยำมากกว่าที่จะพูดว่า "ในรหัส A ถูกวางไว้ก่อน B ภายในเธรด 1 และ C ถูกวางไว้ก่อน D ภายในเธรด 2" จุดประสงค์ของคำถามนี้คือเพื่อยืนยันความคิดนี้
abigaile

ไม่มีการกำหนดไว้ในรูปของ "การจัดลำดับใหม่"
curiousguy

คำตอบ:


12

ฉันเชื่อว่า cppreference นั้นถูกต้อง ผมคิดว่านี่เดือดลงไป "ขณะที่ถ้า" กฎ[intro.execution] / 1 คอมไพเลอร์จะผูกพันเฉพาะในการทำซ้ำพฤติกรรมที่สังเกตได้ของโปรแกรมที่อธิบายโดยรหัสของคุณ ติดใจก่อนที่จะมีความสัมพันธ์ที่จะจัดตั้งขึ้นระหว่างการประเมินผลจากมุมมองของด้ายที่การประเมินเหล่านี้จะดำเนินการ[intro.execution] / 15 นั่นหมายความว่าเมื่อการประเมินสองครั้งติดกันแล้วจะปรากฏที่ใดที่หนึ่งในบางเธรดโค้ดที่รันในเธรดนั้นจะต้องทำตัวราวกับว่าการประเมินครั้งแรกไม่ส่งผลกระทบใด ๆ กับการประเมินครั้งที่สอง ตัวอย่างเช่น

int x = 0;
x = 42;
std::cout << x;

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

ถ้าเราดู

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

ถ้าใช่แล้ว C จะถูกจัดลำดับก่อนหน้า D แต่เมื่อดูจากการแยกหัวข้อนี้ไม่มีอะไรที่ C ส่งผลต่อผลลัพธ์ของ D และไม่มีอะไรที่ D จะเปลี่ยนผลลัพธ์ของ C วิธีเดียวที่จะส่งผลกระทบต่ออีกอย่างหนึ่งคือ เป็นผลทางอ้อมของบางสิ่งบางอย่างที่เกิดขึ้นในหัวข้ออื่น อย่างไรก็ตามโดยการระบุstd::memory_order_relaxedคุณระบุไว้อย่างชัดเจนลำดับที่การโหลดและการจัดเก็บถูกตรวจสอบโดยเธรดอื่นที่ไม่เกี่ยวข้อง เนื่องจากไม่มีเธรดอื่นใดสามารถสังเกตการโหลดและการจัดเก็บตามลำดับใด ๆ จึงไม่มีสิ่งใดที่เธรดอื่นสามารถทำได้เพื่อให้ C และ D ส่งผลกระทบต่อกันในลักษณะที่สอดคล้องกัน ดังนั้นลำดับที่การโหลดและการจัดเก็บจริง ๆ แล้วไม่เกี่ยวข้อง ดังนั้นคอมไพเลอร์มีอิสระที่จะจัดเรียงใหม่ และดังที่กล่าวไว้ในคำอธิบายใต้ตัวอย่างนั้นถ้าร้านค้าจาก D ถูกดำเนินการก่อนที่จะโหลดจาก C ดังนั้น r1 == r2 == 42 สามารถมาถึง ...


โดยพื้นฐานแล้วมาตรฐานระบุว่าC จะต้องเกิดขึ้นก่อน Dแต่คอมไพเลอร์เชื่อว่าไม่สามารถพิสูจน์ได้ว่า C หรือ D เกิดขึ้นต่อไปหรือไม่เนื่องจากกฎตาม - ถ้าเรียงลำดับใหม่ถูกต้องหรือไม่
Fureeish

4
@Freeree No. C ต้องเกิดขึ้นก่อน D เท่าที่เธรดที่เกิดขึ้นสามารถบอก การสังเกตจากบริบทอื่นอาจไม่สอดคล้องกับมุมมองนั้น
Deduplicator

ไม่มี "เช่นถ้ากฎ" เป็น
curiousguy

4
@curiousguy เรียกร้องนี้ดูเหมือนคล้ายกับคนอื่น ๆ ก่อนหน้านี้ evangelisms
Lightness Races ในวงโคจร

1
@crownguy มาตรฐานทำป้ายกำกับข้อใดข้อหนึ่งของมันว่า "กฎ as-if" ในเชิงอรรถ: "บางครั้งบทบัญญัตินี้เรียกว่า" as-if "กฎ" intro.execution
Caleth

1

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

ตัวอย่างเช่นสมมติว่ามีเหตุการณ์สามเหตุการณ์ต่อไปนี้:

  • เก็บ 1 ถึง p1
  • โหลด p2 เป็นอุณหภูมิ
  • เก็บ 2 ถึง p3

และการอ่านของ p2 นั้นได้รับคำสั่งอย่างอิสระหลังจากการเขียนของ p1 และก่อนการเขียนของ p3 แต่ไม่มีการเรียงลำดับใดที่ทั้ง p1 และ p3 มีส่วนร่วม ขึ้นอยู่กับสิ่งที่ทำกับ p2 มันอาจจะเป็นไปไม่ได้ที่คอมไพเลอร์จะเลื่อน p1 ที่ผ่านมา p3 และยังคงบรรลุความหมายที่จำเป็นด้วย p2 อย่างไรก็ตามสมมติว่าคอมไพเลอร์รู้ว่าโค้ดด้านบนเป็นส่วนหนึ่งของลำดับที่มีขนาดใหญ่กว่า:

  • เก็บ 1 ถึง p2 [ติดตั้งก่อนโหลด p2]
  • [ทำข้างต้น]
  • เก็บ 3 ลงใน p1 [ติดอันดับหลังจากที่อีกอันหนึ่งไปยัง p1]

ในกรณีดังกล่าวสามารถระบุได้ว่าสามารถจัดเรียงที่เก็บใหม่เป็น p1 ได้หลังจากโค้ดด้านบนและรวมเข้ากับที่เก็บต่อไปนี้จึงทำให้โค้ดที่เขียน p3 โดยไม่ต้องเขียน p1 ก่อน:

  • ตั้งอุณหภูมิเป็น 1
  • เก็บอุณหภูมิไว้ที่ p2
  • เก็บ 2 ถึง p3
  • เก็บ 3 ถึง p1

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


1

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

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


-7

สิ่งที่คุณคิดว่าถูกต้องเท่าเทียมกัน มาตรฐานไม่ได้บอกสิ่งที่ดำเนินการตามลำดับสิ่งที่ไม่ได้และวิธีการที่จะนำมาผสมกัน

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

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