const หมายถึง thread-safe ใน C ++ 11 หรือไม่?


116

ผมได้ยินว่าconstหมายถึงด้ายปลอดภัยในC ++ 11 เป็นเช่นนั้นจริงหรือ?

หมายความว่าconstตอนนี้เทียบเท่าของJava 's synchronized?

พวกเขาใช้คีย์เวิร์ดหมดหรือไม่


1
คำถามที่พบบ่อยเกี่ยวกับ C ++ - โดยทั่วไปได้รับการจัดการโดยชุมชน C ++ และคุณสามารถมาขอความคิดเห็นจากเราได้ในแชทของเรา
Puppy

@DeadMG: ฉันไม่รู้ถึง C ++ - คำถามที่พบบ่อยและมารยาทของมันถูกแนะนำในความคิดเห็น
K-ballo

2
คุณได้ยินมาจากไหนว่า const แปลว่าเธรดปลอดภัย
Mark B

2
@ Mark B: Herb SutterและBjarne Stroustrupพูดเช่นนั้นที่Standard C ++ Foundationดูลิงก์ที่ด้านล่างของคำตอบ
K-ballo

โปรดทราบว่ามาที่นี่: คำถามจริงไม่ได้const หมายความว่าเธรดปลอดภัยหรือไม่ constนั่นจะเป็นเรื่องไร้สาระตั้งแต่มิฉะนั้นมันจะหมายความว่าคุณควรจะสามารถเพียงไปข้างหน้าและทำเครื่องหมายวิธีด้ายปลอดภัยทุกเป็น แต่คำถามที่เราถามจริงๆคือconst IMPLIES thread-safe และนั่นคือสิ่งที่การสนทนานี้เกี่ยวกับ
user541686

คำตอบ:


132

ผมได้ยินว่าconstหมายถึงด้ายปลอดภัยในC ++ 11 เป็นเช่นนั้นจริงหรือ?

เป็นเรื่องจริงบ้าง ...

นี่คือสิ่งที่ภาษามาตรฐานพูดเกี่ยวกับความปลอดภัยของเธรด:

[1.10 / 4] การประเมินนิพจน์สองรายการขัดแย้งกันหากหนึ่งในนั้นแก้ไขตำแหน่งหน่วยความจำ (1.7) และอีกรายการหนึ่งเข้าถึงหรือแก้ไขตำแหน่งหน่วยความจำเดียวกัน

[1.10 / 21] การดำเนินการของโปรแกรมประกอบด้วยการแย่งชิงข้อมูลหากมีการกระทำที่ขัดแย้งกันสองรายการในเธรดที่แตกต่างกันอย่างน้อยหนึ่งรายการไม่ใช่อะตอมและไม่เกิดขึ้นก่อนหน้าอื่น การแข่งขันข้อมูลดังกล่าวส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด

ซึ่งไม่มีอะไรอื่นนอกจากเงื่อนไขที่เพียงพอสำหรับการแย่งชิงข้อมูล :

  1. มีการดำเนินการสองอย่างหรือมากกว่าในเวลาเดียวกันกับสิ่งที่กำหนด และ
  2. อย่างน้อยหนึ่งในนั้นคืองานเขียน

มาตรฐานห้องสมุดสร้างขึ้นบนที่จะบิตเพิ่มเติม:

[17.6.5.9/1] ส่วนนี้ระบุข้อกำหนดที่การใช้งานจะต้องเป็นไปตามเพื่อป้องกันการแย่งชิงข้อมูล (1.10) ทุกฟังก์ชันของไลบรารีมาตรฐานจะต้องเป็นไปตามข้อกำหนดแต่ละข้อเว้นแต่จะระบุไว้เป็นอย่างอื่น การนำไปใช้งานอาจป้องกันการแย่งข้อมูลในกรณีอื่นนอกเหนือจากที่ระบุไว้ด้านล่าง

[17.6.5.9/3] ฟังก์ชั่นมาตรฐานห้องสมุด c ++ จะไม่ตรงหรือทางอ้อมปรับเปลี่ยนวัตถุ (1.10) สามารถเข้าถึงได้โดยหัวข้ออื่น ๆ กว่าเธรดปัจจุบันเว้นแต่วัตถุที่มีการเข้าถึงโดยตรงหรือโดยอ้อมผ่านทางที่ไม่ใช่ของฟังก์ชัน constthisข้อโต้แย้งรวมทั้ง

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

  1. ประกอบด้วยการอ่านทั้งหมดนั่นคือไม่มีการเขียน -; หรือ
  2. ซิงโครไนซ์การเขียนภายใน

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

หมายความว่าconstตอนนี้เทียบเท่าของJava 's synchronized?

ไม่ ไม่ใช่เลย...

พิจารณาคลาสที่เรียบง่ายเกินไปต่อไปนี้ซึ่งแสดงถึงรูปสี่เหลี่ยมผืนผ้า:

class rect {
    int width = 0, height = 0;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        width = new_width;
        height = new_height;
    }
    int area() const {
        return width * height;
    }
};

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

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

แต่ไม่เป็นไรrectไม่ใช่constอย่างนั้นมันก็ไม่ได้คาดว่าจะปลอดภัยด้วยซ้ำ ในทางกลับกันวัตถุที่ประกาศconst rectจะปลอดภัยต่อเธรดเนื่องจากไม่มีการเขียนใด ๆ ที่เป็นไปได้ (และหากคุณกำลังพิจารณาconst_castสิ่งที่ประกาศไว้ในตอนแรกconstคุณจะได้รับพฤติกรรมที่ไม่ได้กำหนดและนั่นแหล่ะ)

แล้วมันหมายความว่าอย่างไร?

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

class rect {
    int width = 0, height = 0;

    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        cached_area_valid = ( width == new_width && height == new_height );
        width = new_width;
        height = new_height;
    }
    int area() const {
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

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

สมาชิกฟังก์ชั่น areaจะไม่ด้ายปลอดภัยก็จะทำตอนนี้เขียนและไม่ได้มีการประสานภายใน มันเป็นปัญหา? การเรียกไปที่areaอาจเกิดขึ้นโดยเป็นส่วนหนึ่งของตัวสร้างการคัดลอกของวัตถุอื่นตัวสร้างดังกล่าวอาจถูกเรียกโดยการดำเนินการบางอย่างบนคอนเทนเนอร์มาตรฐานและ ณ จุดนั้นไลบรารีมาตรฐานคาดว่าการดำเนินการนี้จะทำงานเป็นการอ่านเกี่ยวกับการแข่งขันข้อมูล . แต่เรากำลังเขียน!

เร็วที่สุดเท่าที่เราใส่rectในภาชนะมาตรฐาน --directly หรือ indirectly-- เรากำลังเข้าสู่การทำสัญญากับมาตรฐานห้องสมุด ในการดำเนินการเขียนในconstฟังก์ชันต่อไปในขณะที่ยังคงปฏิบัติตามสัญญานั้นเราจำเป็นต้องซิงโครไนซ์การเขียนเหล่านั้นภายใน:

class rect {
    int width = 0, height = 0;

    mutable std::mutex cache_mutex;
    mutable int cached_area = 0;
    mutable bool cached_area_valid = true;

public:
    /*...*/
    void set_size( int new_width, int new_height ) {
        if( new_width != width || new_height != height )
        {
            std::lock_guard< std::mutex > guard( cache_mutex );
        
            cached_area_valid = false;
        }
        width = new_width;
        height = new_height;
    }
    int area() const {
        std::lock_guard< std::mutex > guard( cache_mutex );
        
        if( !cached_area_valid ) {
            cached_area = width;
            cached_area *= height;
            cached_area_valid = true;
        }
        return cached_area;
    }
};

โปรดทราบว่าเราได้สร้างareaฟังก์ชันที่ปลอดภัยต่อเธรดแต่rectก็ยังไม่ปลอดภัยต่อเธรด การเรียกที่areaเกิดขึ้นในเวลาเดียวกับที่การโทรไปset_sizeยังอาจยังคงคำนวณค่าผิดเนื่องจากการกำหนดwidthและheightไม่ได้รับการคุ้มครองโดย mutex

ถ้าเราต้องการจริงๆด้ายปลอดภัย rectเราจะใช้การประสานดั้งเดิมเพื่อปกป้องไม่ใช่ด้ายปลอดภัย rect

พวกเขาใช้คีย์เวิร์ดหมดหรือไม่

ใช่พวกเขาเป็น พวกเขาใช้คีย์เวิร์ดหมดตั้งแต่วันแรก


ที่มา : คุณไม่รู้constและmutable - Herb Sutter


6
@ Ben Voigt: มันเป็นความเข้าใจของฉันว่าC ++ 11ข้อกำหนดสำหรับstd::stringเป็นคำพูดในทางที่มีอยู่แล้วห้ามวัว ฉันจำไม่ได้ว่าเฉพาะ ...
K-ballo

3
@BenVoigt: ไม่เพียง แต่จะป้องกันไม่ให้สิ่งดังกล่าวไม่ซิงโครไนซ์เท่านั้นกล่าวคือไม่ปลอดภัยต่อเธรด C ++ 11 ห้ามวัวอย่างชัดเจนแล้ว - ข้อความนี้ไม่เกี่ยวข้องกับเรื่องนั้นและจะไม่ห้ามวัว
Puppy

2
สำหรับฉันแล้วดูเหมือนว่ามีช่องว่างเชิงตรรกะ [17.6.5.9/3] ห้าม "มากเกินไป" โดยพูดว่า "จะต้องไม่แก้ไขโดยตรงหรือโดยอ้อม"; ควรพูดว่า "จะไม่แนะนำการแข่งขันข้อมูลทั้งทางตรงและทางอ้อม" เว้นแต่การเขียนปรมาณูจะถูกกำหนดให้ไม่เป็นการ "แก้ไข" แต่ฉันไม่สามารถหาสิ่งนี้ได้ทุกที่
Andy Prowl

1
ฉันอาจจะทำให้ประเด็นทั้งหมดของฉันชัดเจนขึ้นเล็กน้อยที่นี่: isocpp.org/blog/2012/12/…ขอบคุณที่พยายามช่วยต่อไป
Andy Prowl

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