C ++ 11 แนะนำรุ่นหน่วยความจำที่ได้มาตรฐาน มันหมายความว่าอะไร? แล้วมันจะมีผลกับการเขียนโปรแกรม C ++ อย่างไร?


1894

C ++ 11 นำเสนอโมเดลหน่วยความจำที่ได้มาตรฐาน แต่นั่นหมายความว่าอย่างไร แล้วมันจะมีผลกับการเขียนโปรแกรม C ++ อย่างไร?

บทความนี้ (โดยกาวินคล๊าร์คซึ่งเป็นผู้เสนอราคาHerb Sutter ) กล่าวว่า

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

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

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

โปรแกรมเมอร์ C ++ เคยพัฒนาแอพพลิเคชั่นแบบมัลติเธรดมาก่อนดังนั้นมันจะสำคัญอย่างไรถ้าเป็นเธรด POSIX หรือเธรด Windows หรือเธรด C ++ 11 ประโยชน์คืออะไร ฉันต้องการที่จะเข้าใจรายละเอียดในระดับต่ำ

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

เนื่องจากฉันไม่รู้ว่า internals ของการทำงานแบบมัลติเธรดและรูปแบบหน่วยความจำมีความหมายอย่างไรโดยทั่วไปโปรดช่วยฉันเข้าใจแนวคิดเหล่านี้ :-)


3
@curiousguy: อธิบายอย่างละเอียด ...
Nawaz

4
@currguy: เขียนบล็อกแล้ว ... และเสนอการแก้ไขด้วย ไม่มีวิธีอื่นในการทำให้คะแนนของคุณถูกต้องและมีเหตุผล
Nawaz

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

5
@crownguy: C ++ เป็นสิ่งที่มาตรฐานพูดไม่ใช่สิ่งที่คนที่สุ่มบนอินเทอร์เน็ตพูดว่า ใช่ต้องมีความสอดคล้องกับมาตรฐาน C ++ ไม่ใช่ปรัชญาแบบเปิดที่คุณสามารถพูดคุยเกี่ยวกับสิ่งใดก็ตามที่ไม่ได้เป็นไปตามมาตรฐาน
Nawaz

3
"ฉันพิสูจน์แล้วว่าไม่มีโปรแกรม C ++ ที่สามารถกำหนดพฤติกรรมได้ดี" . การเรียกร้องสูงโดยไม่มีหลักฐานใด ๆ !
Nawaz

คำตอบ:


2204

ก่อนอื่นคุณต้องเรียนรู้ที่จะคิดเหมือนนักกฎหมายภาษา

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

เครื่องนามธรรมในข้อกำหนด C ++ 98 / C ++ 03 เป็นเธรดเดี่ยวแบบพื้นฐาน ดังนั้นจึงเป็นไปไม่ได้ที่จะเขียนโค้ด C ++ แบบหลายเธรดซึ่งเป็น "แบบพกพาได้อย่างเต็มที่" ตามข้อกำหนด สเป็คไม่ได้พูดอะไรเกี่ยวกับatomicityของหน่วยความจำโหลดและเก็บหรือลำดับที่โหลดและเก็บอาจเกิดขึ้นไม่เคยสนใจสิ่งต่าง ๆ เช่น mutexes

แน่นอนคุณสามารถเขียนโค้ดแบบมัลติเธรดในทางปฏิบัติสำหรับระบบคอนกรีตเฉพาะเช่น pthreads หรือ Windows แต่ไม่มีวิธีมาตรฐานในการเขียนโค้ดแบบมัลติเธรดสำหรับ C ++ 98 / C ++ 03

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

พิจารณาตัวอย่างต่อไปนี้โดยที่คู่ของตัวแปรโกลบอลถูกเข้าถึงพร้อมกันโดยสองเธรด:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

เธรดเอาต์พุต 2 อาจมีอะไรบ้าง

ภายใต้ C ++ 98 / C ++ 03 สิ่งนี้ไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนด คำถามนั้นไม่มีความหมายเพราะมาตรฐานไม่คิดสิ่งใดที่เรียกว่า "เธรด"

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

แต่ด้วย C ++ 11 คุณสามารถเขียนสิ่งนี้:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

ตอนนี้สิ่งต่าง ๆ น่าสนใจยิ่งขึ้น แรกของทุกพฤติกรรมที่นี่เป็นที่ที่กำหนดไว้ ตอนนี้เธรด 2 สามารถพิมพ์ได้0 0(หากรันก่อนเธรด 1), 37 17(หากรันหลังเธรด 1) หรือ0 17(หากรันหลังจากเธรด 1 กำหนดให้กับ x แต่ก่อนที่จะกำหนดให้กับ y)

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

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

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

ยิ่งซีพียูมีความทันสมัยมากเท่าไหร่ก็ยิ่งมีโอกาสมากขึ้นที่จะเร็วกว่าตัวอย่างก่อนหน้านี้

สุดท้ายหากคุณต้องการเก็บสินค้าและร้านค้าตามลำดับคุณสามารถเขียน:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

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

แน่นอนถ้าผลลัพธ์เพียงอย่างเดียวที่คุณต้องการดูคือ0 0หรือ37 17คุณสามารถตัด mutex รอบ ๆ รหัสต้นฉบับได้ แต่ถ้าคุณได้อ่านมาไกลขนาดนี้ฉันพนันได้เลยว่าคุณรู้แล้วว่ามันใช้งานได้อย่างไรและคำตอบนี้นานกว่าที่ฉันตั้งใจไว้ :-)

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

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้ดูโพสต์บล็อกนี้


37
คำตอบที่ดี แต่นี่คือสิ่งที่ขอร้องอย่างแท้จริงสำหรับตัวอย่างดั้งเดิมใหม่ นอกจากนี้ฉันคิดว่าการสั่งซื้อหน่วยความจำโดยไม่ต้องมีพื้นฐานเหมือนกับ pre-C ++ 0x: ไม่มีการรับประกัน
John Ripley

5
@ จอห์น: ฉันรู้ แต่ฉันยังคงเรียนรู้พื้นฐานตัวเอง :-) นอกจากนี้ฉันคิดว่าพวกเขารับประกันการเข้าถึงไบต์เป็นอะตอม (แม้ว่าจะไม่ได้สั่ง) ซึ่งเป็นเหตุผลที่ฉันไปกับ "ถ่าน" สำหรับตัวอย่างของฉัน ... แต่ฉันไม่แน่ใจ 100% เกี่ยวกับเรื่องนี้ ... หากคุณต้องการแนะนำที่ดี " การอ้างอิงเกี่ยวกับการกวดวิชา "ฉันจะเพิ่มพวกเขาในคำตอบของฉัน
Nemo

48
@Nawaz: ใช่! การเข้าถึงหน่วยความจำสามารถรับการจัดลำดับใหม่โดยคอมไพเลอร์หรือ CPU คิดถึงแคช (เช่น) และการโหลดแบบเก็งกำไร ลำดับที่หน่วยความจำของระบบได้รับผลกระทบนั้นไม่เหมือนกับสิ่งที่คุณเขียน คอมไพเลอร์และ CPU จะตรวจสอบให้แน่ใจว่าการเรียงลำดับใหม่ไม่ทำลายรหัสเธรดเดียว สำหรับโค้ดแบบมัลติเธรด "โมเดลหน่วยความจำ" จะอธิบายลักษณะการจัดลำดับใหม่ที่เป็นไปได้และจะเกิดอะไรขึ้นถ้าเธรดสองตัวอ่าน / เขียนตำแหน่งเดียวกันในเวลาเดียวกันและวิธีที่คุณควบคุมทั้งสองอย่าง สำหรับโค้ดแบบเธรดเดียวโมเดลหน่วยความจำไม่เกี่ยวข้อง
Nemo

26
@Nawaz, @Nemo - รายละเอียดเล็ก ๆ น้อย ๆ : รุ่นหน่วยความจำใหม่ที่มีความเกี่ยวข้องในรหัสเดียวเกลียวตราบเท่าที่มันระบุ undefinedness i = i++ของการแสดงออกบางอย่างเช่น แนวคิดแบบเก่าของคะแนนลำดับได้ถูกยกเลิกไปแล้ว มาตรฐานใหม่ระบุสิ่งเดียวกันโดยใช้ความสัมพันธ์แบบซีเควนซ์ก่อนซึ่งเป็นกรณีพิเศษของแนวคิดอินเตอร์เธรดทั่วไปที่เกิดขึ้นก่อนแนวคิด
JohannesD

17
@ AJG85: ส่วน 3.6.2 ของสเปค C ++ 0x ฉบับร่างกล่าวว่า "ตัวแปรที่มีระยะเวลาการจัดเก็บแบบคงที่ (3.7.1) หรือระยะเวลาการจัดเก็บด้าย (3.7.2) ต้องเป็นศูนย์เริ่มต้น (8.5) ก่อนที่การเริ่มต้นอื่น ๆ สถานที่." เนื่องจาก x, y เป็นโกลบอลในตัวอย่างนี้พวกเขามีระยะเวลาการจัดเก็บแบบคงที่และดังนั้นจึงจะเริ่มต้นได้เป็นศูนย์ฉันเชื่อว่า
Nemo

345

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

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

การอ้างอิงจาก"A Primer on Memory Consistency และ Cache Coherence"

โมเดลหน่วยความจำที่ใช้งานง่าย (และ จำกัด มากที่สุด) คือความสอดคล้องตามลำดับ (SC) ซึ่งการประมวลผลแบบมัลติเธรดควรมีลักษณะเหมือนการแทรกซึมของการประมวลผลตามลำดับของแต่ละเธรดที่เป็นส่วนประกอบราวกับว่าเธรดนั้น

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

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

[ภาพจาก Wikipedia] รูปภาพจาก Wikipedia

ผู้อ่านที่คุ้นเคยกับทฤษฎีสัมพัทธภาพพิเศษของ Einstein จะสังเกตเห็นสิ่งที่ฉันพูดพาดพิงถึง การแปลคำพูดของ Minkowski ลงในโมเดลหน่วยความจำ realm: พื้นที่ที่อยู่และเวลาคือเงาของที่อยู่ - เวลา - พื้นที่ ในกรณีนี้ผู้สังเกตการณ์แต่ละคน (เช่นเธรด) จะฉายเงาของเหตุการณ์ (กล่าวคือหน่วยความจำเก็บ / โหลด) บนเส้นโลกของเขาเอง (เช่นแกนเวลาของเขา) และระนาบของเขาพร้อมกัน (แกนพื้นที่ที่อยู่ของเขา) . หัวข้อในรูปแบบหน่วยความจำ C ++ 11 สอดคล้องกับผู้สังเกตการณ์ที่กำลังเคลื่อนไหวสัมพันธ์กับแต่ละอื่น ๆ ในทฤษฎีสัมพัทธภาพพิเศษ ความสอดคล้องตามลำดับสอดคล้องกับกาลอวกาศกาลิลี (กล่าวคือผู้สังเกตการณ์ทุกคนเห็นด้วยกับลำดับเหตุการณ์และความรู้สึกของโลกพร้อมกัน)

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

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

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

เพื่อให้คำจำกัดความของความสอดคล้องของหน่วยความจำและแรงจูงใจในการละทิ้ง SC ฉันจะอ้างอิงจาก"A Primer on Memory Consistency และ Cache Coherence"

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

แบบจำลองความสอดคล้องของหน่วยความจำที่ผ่อนคลายหรืออ่อนแรงบันดาลใจจากข้อเท็จจริงที่ว่าการสั่งซื้อหน่วยความจำส่วนใหญ่ในรุ่นที่แข็งแกร่งนั้นไม่จำเป็น ถ้าเธรดอัพเดตไอเท็มข้อมูลสิบรายการจากนั้นแฟล็กการซิงโครไนซ์โปรแกรมเมอร์มักจะไม่สนใจว่าไอเท็มข้อมูลถูกอัพเดตตามลำดับซึ่งเกี่ยวข้องกัน แต่เฉพาะไอเท็มข้อมูลทั้งหมดที่ถูกอัพเดตก่อนการอัพเดตแฟล็ก ) รุ่นที่ผ่อนคลายพยายามที่จะจับความยืดหยุ่นในการสั่งซื้อที่เพิ่มขึ้นนี้และรักษาเฉพาะคำสั่งที่โปรแกรมเมอร์ต้องการ” เพื่อให้ได้ประสิทธิภาพที่สูงขึ้นและความถูกต้องของ SC ตัวอย่างเช่นในบางสถาปัตยกรรม FIFO เขียนบัฟเฟอร์จะถูกใช้โดยแต่ละคอร์เพื่อเก็บผลลัพธ์ของการจัดเก็บ (เกษียณ) ก่อนที่จะเขียนผลลัพธ์ไปยังแคช การเพิ่มประสิทธิภาพนี้ช่วยเพิ่มประสิทธิภาพ แต่ละเมิด SC บัฟเฟอร์การเขียนซ่อนความล่าช้าในการซ่อมบำรุงร้านค้าที่พลาด เนื่องจากร้านค้าเป็นเรื่องธรรมดาการหลีกเลี่ยงการหยุดร้านค้าในร้านค้าส่วนใหญ่จึงเป็นเรื่องสำคัญ สำหรับโปรเซสเซอร์แบบคอร์เดียวบัฟเฟอร์การเขียนสามารถทำให้มองไม่เห็นสถาปัตยกรรมโดยการตรวจสอบให้แน่ใจว่าโหลดไปยังที่อยู่ A ส่งคืนค่าของที่เก็บล่าสุดไปที่ A แม้ว่าหนึ่งหรือมากกว่าหนึ่งร้านค้าไปยัง A อยู่ในบัฟเฟอร์การเขียน โดยทั่วไปแล้วจะทำได้โดยการข้ามค่าของร้านค้าล่าสุดไปยัง A ไปยังโหลดจาก A โดยที่ "คำสั่งล่าสุด" จะถูกกำหนดตามลำดับโปรแกรม หรือโดยถ่วงโหลด A ถ้าที่เก็บไปยัง A อยู่ในบัฟเฟอร์การเขียน เมื่อใช้หลายคอร์แต่ละคอร์จะมีบัฟเฟอร์การเขียนข้ามของตนเอง หากไม่มีบัฟเฟอร์การเขียนฮาร์ดแวร์คือ SC แต่ด้วยบัฟเฟอร์การเขียนไม่ใช่การทำให้บัฟเฟอร์การเขียนสามารถมองเห็นได้ในสถาปัตยกรรมในโปรเซสเซอร์แบบมัลติคอร์

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

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

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

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


52
+1 สำหรับการเปรียบเทียบที่มีสัมพัทธภาพพิเศษฉันพยายามทำสิ่งเดียวกันกับตัวเอง บ่อยครั้งที่ฉันเห็นโปรแกรมเมอร์เขียนโค้ดเธรดที่พยายามตีความพฤติกรรมขณะที่การดำเนินการในเธรดต่าง ๆ ที่เกิดขึ้นสอดแทรกกันในลำดับที่เฉพาะเจาะจงและฉันต้องบอกพวกเขาไม่กับระบบมัลติโปรเซสเซอร์ความคิดของความพร้อมกันระหว่าง <s > เฟรมของการอ้างอิง </s> กระทู้ตอนนี้ไม่มีความหมาย การเปรียบเทียบกับสัมพัทธภาพพิเศษเป็นวิธีที่ดีในการทำให้พวกเขาเคารพความซับซ้อนของปัญหา
Pierre Lebeaupin

71
ดังนั้นคุณควรสรุปว่าจักรวาลเป็นมัลติคอร์?
Peter K

6
@PeterK: แน่นอน :) และนี่คือการสร้างภาพที่ดีมากของภาพเวลานี้โดยนักฟิสิกส์ Brian Greene: youtube.com/watch?v=4BjGWLJNPcA&t=22m12s นี่คือ "The Illusion of Time [Full Documentary]" ในนาทีที่ 22 และ 12 วินาที
Ahmed Nassar

2
มันเป็นแค่ฉันหรือเขาเปลี่ยนจากรูปแบบหน่วยความจำ 1D (แกนนอน) เป็นรูปแบบหน่วยความจำ 2D (ระนาบของพร้อมกัน) ฉันพบว่ามันสับสนเล็กน้อย แต่อาจเป็นเพราะฉันไม่ใช่เจ้าของภาษา ... ยังเป็นเรื่องที่น่าสนใจมากสำหรับการอ่าน
ลา SE

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

115

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

Sutter สมุนไพรมีสามชั่วโมงคุยยาวเกี่ยวกับซี ++ หน่วยความจำแบบ 11 หัวข้อ "อะตอม <> อาวุธ" ที่มีอยู่บนเว็บไซต์ Channel9 - ส่วนที่ 1และส่วนที่ 2 การพูดคุยทางเทคนิคค่อนข้างดีและครอบคลุมหัวข้อต่อไปนี้:

  1. การเพิ่มประสิทธิภาพการแข่งขันและโมเดลหน่วยความจำ
  2. การสั่งซื้อ - อะไร: รับและปล่อย
  3. การสั่งซื้อ - วิธี: Mutexes, Atomics และ / หรือรั้ว
  4. ข้อ จำกัด อื่น ๆ ในคอมไพเลอร์และฮาร์ดแวร์
  5. Code Gen & Performance: x86 / x64, IA64, POWER, ARM
  6. Atomics ที่ผ่อนคลาย

การพูดไม่ได้อธิบายอย่างละเอียดใน API แต่เกี่ยวกับเหตุผลเบื้องหลังภายใต้ประทุนและเบื้องหลัง (คุณทราบหรือไม่ว่าความหมายที่ผ่อนคลายถูกเพิ่มลงในมาตรฐานเท่านั้นเนื่องจาก POWER และ ARM ไม่รองรับการโหลดแบบซิงโครไนซ์อย่างมีประสิทธิภาพ?)


10
การพูดคุยนั้นยอดเยี่ยมจริง ๆ คุ้มค่ากับเวลา 3 ชั่วโมงที่คุณจะใช้ดู
ZunTzu

5
@ZunTzu: สำหรับเครื่องเล่นวิดีโอส่วนใหญ่คุณสามารถตั้งค่าความเร็วเป็น 1.25, 1.5 หรือ 2 เท่าของต้นฉบับได้
Christian Severin

4
@eran พวกคุณมีสไลด์ไหม? ลิงก์บนหน้าพูดคุยช่อง 9 ไม่ทำงาน
athos

2
@THOS ฉันไม่มีพวกเขาขอโทษ ลองติดต่อช่อง 9 ฉันไม่คิดว่าการลบนั้นจะเป็นการจงใจ (ฉันเดาว่าพวกเขามีลิงก์จาก Herb Sutter โพสต์ตามเดิมและหลังจากนั้นเขาก็ลบไฟล์ออก แต่นั่นเป็นเพียงการเก็งกำไร ... )
Eran

75

หมายความว่ามาตรฐานกำหนดมัลติเธรดและกำหนดสิ่งที่เกิดขึ้นในบริบทของเธรดหลายเธรด แน่นอนคนใช้การใช้งานที่แตกต่างกัน แต่ก็เหมือนถามว่าทำไมเราควรมีstd::stringเมื่อเราทุกคนสามารถใช้stringชั้นเรียนที่บ้านรีด

เมื่อคุณกำลังพูดถึง POSIX เธรดหรือเธรด Windows นี่เป็นภาพลวงตาเล็กน้อยเช่นเดียวกับที่คุณกำลังพูดถึง x86 เธรดเนื่องจากเป็นฟังก์ชันฮาร์ดแวร์ที่ทำงานพร้อมกัน รุ่นหน่วยความจำ C ++ 0x รับประกันได้ไม่ว่าคุณจะใช้ x86 หรือ ARM หรือMIPSหรืออะไรก็ตามที่คุณสามารถหาได้


28
เธรด Posix ไม่ถูก จำกัด ที่ x86 แท้จริงแล้วระบบแรกที่ใช้งานนั้นอาจไม่ใช่ระบบ x86 เธรด Posix นั้นไม่ขึ้นกับระบบและใช้ได้กับแพลตฟอร์ม Posix ทั้งหมด มันยังไม่เป็นความจริงที่เป็นคุณสมบัติของฮาร์ดแวร์เพราะเธรด Posix สามารถนำไปใช้ผ่านการทำงานหลายอย่างพร้อมกันได้ แต่แน่นอนว่าเธรดส่วนใหญ่จะพบปัญหาเฉพาะในการปรับใช้เธรดของฮาร์ดแวร์ (และบางตัวอาจใช้กับระบบมัลติโพรเซสเซอร์
celtschk

57

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

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

น่าสนใจคอมไพเลอร์ Microsoft สำหรับ C ++ ได้รับ / เผยแพร่ semantics สำหรับ volatile ซึ่งเป็นส่วนเสริม C ++ เพื่อจัดการกับการขาดโมเดลหน่วยความจำใน C ++ http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs .80) อย่างไรก็ตามเนื่องจาก Windows ทำงานบน x86 / x64 เท่านั้นมันไม่ได้พูดอะไรมาก (โมเดลหน่วยความจำ Intel และ AMD ทำให้ง่ายและมีประสิทธิภาพในการติดตั้ง semantics ในภาษาที่ได้รับ / เผยแพร่)


2
มันเป็นความจริงว่าเมื่อเขียนคำตอบแล้ว Windows จะทำงานบน x86 / x64 เท่านั้น แต่ Windows จะทำงานในบางช่วงเวลาบน IA64, MIPS, Alpha AXP64, PowerPC และ ARM วันนี้มันทำงานบน ARM รุ่นต่างๆซึ่งเป็นหน่วยความจำที่ค่อนข้างแตกต่างจาก x86 และไม่มีที่ไหนให้อภัย
Lorenzo Dematté

ลิงก์ดังกล่าวค่อนข้างใช้งานไม่ได้ (กล่าวว่า"เอกสารประกอบการเกษียณอายุของ Visual Studio 2005" ) สนใจที่จะอัปเดตหรือไม่
Peter Mortensen

3
มันไม่เป็นความจริงแม้ว่าจะเขียนคำตอบแล้วก็ตาม
Ben

" การเข้าถึงหน่วยความจำเดียวกันพร้อม ๆ กัน " ในการเข้าถึงในความขัดแย้งทาง
curiousguy

27

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

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

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


19
ปัญหาก่อนหน้านี้คือไม่มีสิ่งเช่น mutex (ในแง่ของมาตรฐาน C ++) ดังนั้นการรับประกันเดียวที่คุณให้ไว้คือผู้ผลิต mutex ซึ่งใช้ได้ตราบใดที่คุณไม่ได้เปลี่ยนรหัส (เนื่องจากการเปลี่ยนแปลงเล็กน้อยของการรับประกันเป็นการยากที่จะมองเห็น) ตอนนี้เราได้รับการรับประกันตามมาตรฐานซึ่งควรพกพาระหว่างแพลตฟอร์ม
Martin York

4
@ มาร์ติน: ในกรณีใด ๆ สิ่งหนึ่งคือโมเดลหน่วยความจำและอีกอย่างคืออะตอมมิกและเธรดแบบดั้งเดิมซึ่งทำงานอยู่ด้านบนของโมเดลหน่วยความจำนั้น
ninjalj

4
นอกจากนี้ประเด็นของฉันก็คือส่วนใหญ่ที่ก่อนหน้านี้ส่วนใหญ่ไม่มีรุ่นหน่วยความจำในระดับภาษามันเกิดขึ้นเป็นรูปแบบหน่วยความจำของ CPU พื้นฐาน ขณะนี้มีโมเดลหน่วยความจำซึ่งเป็นส่วนหนึ่งของภาษาหลัก OTOH, mutexes และสิ่งที่คล้ายกันสามารถทำได้เช่นเดียวกับห้องสมุด
ninjalj

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

11
น่าเสียดายที่มันไม่เพียงพอที่จะปกป้องโครงสร้างข้อมูลของคุณด้วย mutexes แบบง่าย ๆ หากไม่มีหน่วยความจำที่สอดคล้องกันในภาษาของคุณ มีการปรับแต่งคอมไพเลอร์ที่เหมาะสมในบริบทเธรดเดียว แต่เมื่อหลายเธรดและแกน cpu เข้ามาเล่นการเรียงลำดับของการเข้าถึงหน่วยความจำและการเพิ่มประสิทธิภาพอื่น ๆ อาจทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด สำหรับข้อมูลเพิ่มเติมโปรดดู "หัวข้อไม่สามารถนำไปใช้เป็นห้องสมุด" โดย Hans Boehm: citeseer.ist.psu.edu/viewdoc/ ......
exDM69

0

คำตอบข้างต้นจะได้รับในแง่มุมพื้นฐานที่สุดของโมเดลหน่วยความจำ C ++ ในทางปฏิบัติการใช้งานส่วนใหญ่ของstd::atomic<>"ทำงานแค่" อย่างน้อยก็จนกว่าโปรแกรมเมอร์จะปรับให้เหมาะสมที่สุด (เช่นพยายามทำหลายสิ่งมากเกินไป)

มีสถานที่หนึ่งที่ผิดพลาดยังคงมีเหมือนกันคือ: ล็อคลำดับ มีการสนทนาที่ดีและง่ายต่อการอ่านของความท้าทายที่https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf การล็อคลำดับนั้นน่าดึงดูดเพราะผู้อ่านหลีกเลี่ยงการเขียนไปที่คำล็อค รหัสต่อไปนี้เป็นไปตามรูปที่ 1 ของรายงานทางเทคนิคข้างต้นและจะเน้นถึงความท้าทายเมื่อใช้การล็อคลำดับใน C ++:

atomic<uint64_t> seq; // seqlock representation
int data1, data2;     // this data will be protected by seq

T reader() {
    int r1, r2;
    unsigned seq0, seq1;
    while (true) {
        seq0 = seq;
        r1 = data1; // INCORRECT! Data Race!
        r2 = data2; // INCORRECT!
        seq1 = seq;

        // if the lock didn't change while I was reading, and
        // the lock wasn't held while I was reading, then my
        // reads should be valid
        if (seq0 == seq1 && !(seq0 & 1))
            break;
    }
    use(r1, r2);
}

void writer(int new_data1, int new_data2) {
    unsigned seq0 = seq;
    while (true) {
        if ((!(seq0 & 1)) && seq.compare_exchange_weak(seq0, seq0 + 1))
            break; // atomically moving the lock from even to odd is an acquire
    }
    data1 = new_data1;
    data2 = new_data2;
    seq = seq0 + 2; // release the lock by increasing its value to even
}

ในฐานะที่เป็น unintuitive มัน seams ในตอนแรกdata1และจำเป็นที่จะต้องdata2 atomic<>หากพวกเขาไม่ใช่ปรมาณูพวกเขาก็สามารถอ่าน (ในreader()) ในเวลาเดียวกับที่พวกเขาเขียน (ในwriter()) ตามโมเดลหน่วยความจำ C ++ นี่เป็นการแข่งขันแม้ว่าจะreader()ไม่ได้ใช้ข้อมูลจริง นอกจากนี้หากพวกเขาไม่ใช่อะตอมคอมไพเลอร์สามารถแคชอ่านครั้งแรกของแต่ละค่าในการลงทะเบียน เห็นได้ชัดว่าคุณไม่ต้องการที่ ... คุณต้องการอ่านซ้ำในการwhileวนซ้ำในreader()แต่ละ

นอกจากนี้ยังไม่เพียงพอที่จะทำให้พวกเขาและการเข้าถึงพวกเขาด้วยatomic<> memory_order_relaxedเหตุผลสำหรับสิ่งนี้คือการอ่าน seq (ในreader()) ได้รับความหมายเท่านั้น ในแง่ง่ายๆถ้า X และ Y เป็นการเข้าถึงหน่วยความจำ X นำหน้า Y, X ไม่ใช่การได้มาหรือการปลดปล่อยและ Y คือการได้รับคอมไพเลอร์สามารถเรียงลำดับ Y ใหม่ก่อน X ถ้า Y เป็นครั้งที่สองที่อ่าน seq และ X เป็นข้อมูลที่อ่านการเรียงลำดับใหม่จะทำให้การใช้งานล็อคไม่ทำงาน

กระดาษให้คำตอบเล็กน้อย อันที่มีประสิทธิภาพดีที่สุดในวันนี้น่าจะเป็นอันที่ใช้atomic_thread_fenceกับmemory_order_relaxed ก่อนที่จะอ่าน seqlock ครั้งที่สอง ในกระดาษมันเป็นรูปที่ 6 ฉันไม่ได้ทำซ้ำรหัสที่นี่เพราะทุกคนที่ได้อ่านสิ่งนี้ไกลควรอ่านกระดาษ แม่นยำและสมบูรณ์กว่าโพสต์นี้

ปัญหาสุดท้ายคือมันอาจผิดธรรมชาติในการสร้างdataตัวแปรอะตอม หากคุณไม่สามารถอยู่ในรหัสของคุณได้คุณจะต้องระมัดระวังเป็นอย่างมากเพราะการคัดเลือกจาก non-atomic to atomic นั้นเป็นไปตามกฎหมายสำหรับประเภทดั้งเดิมเท่านั้น ควรเพิ่ม C ++ 20 atomic_ref<>ซึ่งจะทำให้ปัญหานี้แก้ไขได้ง่ายขึ้น

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


-2

C และ C ++ ใช้เพื่อกำหนดโดยการติดตามการดำเนินการของโปรแกรมที่มีรูปแบบที่ดี

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

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


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

1
@MatiasHaeussler ฉันคิดว่าคุณเข้าใจคำตอบของฉันผิด ฉันไม่ได้คัดค้านคำจำกัดความของคุณลักษณะเฉพาะ C ++ ที่นี่ (ฉันยังมีการวิพากษ์วิจารณ์แบบแหลมจำนวนมาก แต่ไม่ใช่ที่นี่) ฉันโต้เถียงที่นี่ว่าไม่มีการกำหนดที่ชัดเจนใน C ++ (หรือ C) ความหมายทั้งหมดของ MT นั้นยุ่งเหยิงอย่างสิ้นเชิงเนื่องจากคุณไม่มีความหมายแบบต่อเนื่องอีกต่อไป (ฉันเชื่อว่า Java MT ใช้งานไม่ได้ แต่น้อยกว่า) "ตัวอย่างง่ายๆ" จะเป็นโปรแกรม MT เกือบทุกตัว หากคุณไม่เห็นคุณจะยินดีที่จะตอบคำถามของฉันเกี่ยวกับวิธีการที่จะพิสูจน์ความถูกต้องของมอนแทนา c ++ โปรแกรม
curiousguy

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

1
"ควรมีคำถามเกี่ยวกับความสามารถในการทำลายล้างของโปรแกรมคอมพิวเตอร์ใน stackoverflow หรือ stackexchange (ถ้าไม่ใช่, ที่ไหน)" อันนี้ดูเหมือนจะเป็นหนึ่งสำหรับเมตาสแต็คโอเวอร์โฟลว์ใช่ไหม?
Matias Haeussler

1
@MatiasHaeussler 1) C และ C ++ เป็นหลักแบ่งปัน "หน่วยความจำแบบจำลอง" ของตัวแปรอะตอม, mutexes และมัลติเธรด 2) ความเกี่ยวข้องในเรื่องนี้เป็นเรื่องเกี่ยวกับประโยชน์ของการมี "รูปแบบหน่วยความจำ" ฉันคิดว่าผลประโยชน์มีค่าเป็นศูนย์เนื่องจากโมเดลไม่ปลอดภัย
curiousguy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.