ผมได้ยินว่าconst
หมายถึงด้ายปลอดภัยในC ++ 11 เป็นเช่นนั้นจริงหรือ?
เป็นเรื่องจริงบ้าง ...
นี่คือสิ่งที่ภาษามาตรฐานพูดเกี่ยวกับความปลอดภัยของเธรด:
[1.10 / 4]
การประเมินนิพจน์สองรายการขัดแย้งกันหากหนึ่งในนั้นแก้ไขตำแหน่งหน่วยความจำ (1.7) และอีกรายการหนึ่งเข้าถึงหรือแก้ไขตำแหน่งหน่วยความจำเดียวกัน
[1.10 / 21]
การดำเนินการของโปรแกรมประกอบด้วยการแย่งชิงข้อมูลหากมีการกระทำที่ขัดแย้งกันสองรายการในเธรดที่แตกต่างกันอย่างน้อยหนึ่งรายการไม่ใช่อะตอมและไม่เกิดขึ้นก่อนหน้าอื่น การแข่งขันข้อมูลดังกล่าวส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนด
ซึ่งไม่มีอะไรอื่นนอกจากเงื่อนไขที่เพียงพอสำหรับการแย่งชิงข้อมูล :
- มีการดำเนินการสองอย่างหรือมากกว่าในเวลาเดียวกันกับสิ่งที่กำหนด และ
- อย่างน้อยหนึ่งในนั้นคืองานเขียน
มาตรฐานห้องสมุดสร้างขึ้นบนที่จะบิตเพิ่มเติม:
[17.6.5.9/1]
ส่วนนี้ระบุข้อกำหนดที่การใช้งานจะต้องเป็นไปตามเพื่อป้องกันการแย่งชิงข้อมูล (1.10) ทุกฟังก์ชันของไลบรารีมาตรฐานจะต้องเป็นไปตามข้อกำหนดแต่ละข้อเว้นแต่จะระบุไว้เป็นอย่างอื่น การนำไปใช้งานอาจป้องกันการแย่งข้อมูลในกรณีอื่นนอกเหนือจากที่ระบุไว้ด้านล่าง
[17.6.5.9/3]
ฟังก์ชั่นมาตรฐานห้องสมุด c ++ จะไม่ตรงหรือทางอ้อมปรับเปลี่ยนวัตถุ (1.10) สามารถเข้าถึงได้โดยหัวข้ออื่น ๆ กว่าเธรดปัจจุบันเว้นแต่วัตถุที่มีการเข้าถึงโดยตรงหรือโดยอ้อมผ่านทางที่ไม่ใช่ของฟังก์ชัน constthis
ข้อโต้แย้งรวมทั้ง
ซึ่งพูดง่ายๆก็คือคาดว่าการดำเนินการกับconst
วัตถุจะปลอดภัยต่อเธรด ซึ่งหมายความว่าไลบรารีมาตรฐานจะไม่แนะนำการแข่งขันข้อมูลตราบใดที่การดำเนินการกับอconst
อบเจ็กต์ประเภทของคุณเอง
- ประกอบด้วยการอ่านทั้งหมดนั่นคือไม่มีการเขียน -; หรือ
- ซิงโครไนซ์การเขียนภายใน
หากการคาดการณ์นี้ไม่ถือสำหรับหนึ่งในประเภทของคุณแล้วใช้มันตรงหรือทางอ้อมร่วมกับส่วนประกอบของใด ๆห้องสมุดมาตรฐานอาจส่งผลให้การแข่งขันข้อมูล โดยสรุป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