ก่อนอื่นคุณต้องเรียนรู้ที่จะคิดเหมือนนักกฎหมายภาษา
ข้อมูลจำเพาะ 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 และตัวแปรเงื่อนไข นั่นคือสิ่งที่ฉันตั้งใจจะทำ
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้ดูโพสต์บล็อกนี้