การใช้งานต่อไปนี้โดยใช้การเริ่มต้นขี้เกียจของSingleton
เธรด (Meyers 'Singleton) ปลอดภัยหรือไม่
static Singleton& instance()
{
static Singleton s;
return s;
}
ถ้าไม่ทำไมและวิธีที่จะทำให้มันปลอดภัยไหม?
การใช้งานต่อไปนี้โดยใช้การเริ่มต้นขี้เกียจของSingleton
เธรด (Meyers 'Singleton) ปลอดภัยหรือไม่
static Singleton& instance()
{
static Singleton s;
return s;
}
ถ้าไม่ทำไมและวิธีที่จะทำให้มันปลอดภัยไหม?
คำตอบ:
ในC ++ 11เป็นเธรดที่ปลอดภัย ตามที่มาตรฐาน , §6.7 [stmt.dcl] p4
:
หากการควบคุมเข้าสู่การประกาศพร้อมกันในขณะที่ตัวแปรกำลังเริ่มต้นการดำเนินการพร้อมกันจะต้องรอให้การเตรียมใช้งานเสร็จสิ้น
รองรับ GCC และ VS สำหรับคุณสมบัติ (การกำหนดค่าเริ่มต้นและการทำลายแบบไดนามิกด้วยการทำงานพร้อมกันหรือที่เรียกว่าMagic Statics บน MSDN ) มีดังนี้:
ขอบคุณ @Mankarse และ @olen_gam สำหรับความคิดเห็นของพวกเขา
ในC ++ 03รหัสนี้ไม่ปลอดภัยสำหรับเธรด มีบทความโดย Meyers เรียกว่า"C ++ และ Perils of Double-Checked Locking"ซึ่งกล่าวถึงการใช้งานรูปแบบที่ปลอดภัยของเธรดและข้อสรุปคือมากหรือน้อยนั้น (ใน C ++ 03) การล็อคเต็มรอบวิธีการสร้างอินสแตนซ์ โดยทั่วไปแล้วเป็นวิธีที่ง่ายที่สุดในการตรวจสอบความพร้อมกันที่เหมาะสมบนแพลตฟอร์มทั้งหมดในขณะที่รูปแบบส่วนใหญ่ของรูปแบบการล็อคแบบตรวจสอบอีกครั้งอาจได้รับผลกระทบจากสภาพการแข่งขันในสถาปัตยกรรมบางประเภทยกเว้นว่าคำแนะนำ
เพื่อตอบคำถามของคุณเกี่ยวกับสาเหตุที่ไม่ใช่เธรดที่ปลอดภัยไม่ใช่เพราะการเรียกครั้งแรกที่จะinstance()
ต้องโทรหาคอนสตรัคSingleton s
เตอร์ เพื่อให้เป็น threadsafe สิ่งนี้จะต้องเกิดขึ้นในส่วนที่สำคัญและไม่มีข้อกำหนดใด ๆ ในมาตรฐานที่จะใช้หัวข้อที่สำคัญ (มาตรฐานจนถึงปัจจุบันเงียบในหัวข้อทั้งหมด) คอมไพเลอร์มักใช้สิ่งนี้โดยใช้การตรวจสอบที่ง่ายและการเพิ่มบูลีนสแตติก - แต่ไม่ใช่ในส่วนที่สำคัญ สิ่งที่ต้องการ pseudocode ต่อไปนี้:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
ดังนั้นนี่คือ Singleton แบบปลอดภัยเธรด (สำหรับ Windows) มันใช้ wrapper คลาสง่าย ๆ สำหรับวัตถุ Windows CRITICAL_SECTION เพื่อให้เราสามารถคอมไพเลอร์เริ่มต้นโดยอัตโนมัติCRITICAL_SECTION
ก่อนที่main()
จะเรียกว่า เป็นการดีที่จะใช้คลาสส่วนวิกฤตที่สำคัญอย่างแท้จริงของ RAII ซึ่งสามารถจัดการกับข้อยกเว้นที่อาจเกิดขึ้นเมื่อมีการจัดหมวดส่วนที่สำคัญ แต่นั่นไม่ได้อยู่เหนือขอบเขตของคำตอบนี้
การดำเนินการขั้นพื้นฐานคือเมื่อมีการSingleton
ร้องขออินสแตนซ์ของการล็อคถูกนำมาใช้ซิงเกิลตันจะถูกสร้างขึ้นถ้ามันจำเป็นจะต้องจากนั้นล็อคจะถูกปล่อยและการอ้างอิง Singleton กลับมา
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
ชาย - นั่นเป็นเรื่องอึมากที่จะ "ทำให้โลกดีขึ้น"
ข้อเสียเปรียบหลักในการนำไปใช้นี้ (ถ้าฉันไม่ปล่อยให้แมลงบางตัวผ่าน) คือ:
new Singleton()
พ่นจะไม่ปล่อยล็อค สิ่งนี้สามารถแก้ไขได้โดยใช้วัตถุล็อค RAII ที่แท้จริงแทนที่จะเป็นแบบธรรมดาที่ฉันมีที่นี่ นอกจากนี้ยังสามารถช่วยทำให้อุปกรณ์พกพาได้ถ้าคุณใช้สิ่งที่คล้ายกับ Boost เพื่อจัดเตรียม wrapper อิสระสำหรับล็อคแพลตฟอร์มmain()
ถูกเรียก - ถ้าคุณเรียกมันก่อนหน้านั้น (เช่นในการเริ่มต้นของวัตถุคงที่) สิ่งต่าง ๆ อาจไม่ทำงานเพราะCRITICAL_SECTION
อาจไม่ได้เริ่มต้นnew Singleton()
พ่น
new Singleton()
มีปัญหาเกิดขึ้นกับล็อค ควรใช้คลาสล็อค RAII ที่เหมาะสมซึ่งคล้ายกับlock_guard
Boost ฉันต้องการตัวอย่างที่จะมีอยู่ในตัวเองมากขึ้นหรือน้อยลงและมันก็เป็นสัตว์ประหลาดอยู่เล็กน้อยดังนั้นฉันจึงออกจากความปลอดภัยยกเว้น (แต่เรียกมันออกมา) บางทีฉันควรแก้ไขเพื่อให้รหัสนี้ไม่ได้ถูกตัดวางที่อื่นที่ไม่เหมาะสม
มองไปที่มาตรฐานถัดไป (ส่วนที่ 6.7.4) มันจะอธิบายว่าการกำหนดค่าเริ่มต้นแบบคงที่ในท้องถิ่นนั้นมีความปลอดภัยต่อเธรด ดังนั้นเมื่อส่วนของมาตรฐานนั้นถูกนำไปใช้อย่างกว้างขวางแล้วซิงเกิลของเมเยอร์จะเป็นการนำไปใช้ที่ต้องการ
ฉันไม่เห็นด้วยกับคำตอบมากมายแล้ว คอมไพเลอร์ส่วนใหญ่ใช้การเริ่มต้นคงที่ด้วยวิธีนี้ ข้อยกเว้นหนึ่งที่น่าสังเกตคือ Microsoft Visual Studio
คำตอบที่ถูกต้องขึ้นอยู่กับคอมไพเลอร์ของคุณ มันสามารถตัดสินใจที่จะทำให้มันปลอดภัย มันไม่ใช่ threadsafe "naturallly"
การใช้งานต่อไปนี้ [... ] เธรดปลอดภัยหรือไม่
บนแพลตฟอร์มส่วนใหญ่สิ่งนี้จะไม่ปลอดภัยสำหรับเธรด (ผนวกข้อจำกัดความรับผิดชอบตามปกติอธิบายว่ามาตรฐาน C ++ ไม่รู้เกี่ยวกับเธรดดังนั้นตามกฎหมายมันไม่ได้บอกว่ามันเป็นหรือไม่)
ถ้าไม่ใช่ทำไม [... ]?
เหตุผลก็คือไม่มีสิ่งใดที่ป้องกันไม่ให้เธรดมากกว่าหนึ่งเธรดดำเนินการตัวs
สร้าง ' พร้อมกัน
วิธีที่จะทำให้มันปลอดภัยไหม?
"C ++ และภัยคุกคามจากการล็อคเช็คที่สองครั้ง"โดย Scott Meyers และ Andrei Alexandrescu เป็นบทความที่ค่อนข้างดีในเรื่องของ singletons ที่ปลอดภัยสำหรับเธรด
ดังที่ MSalters กล่าวว่า: ขึ้นอยู่กับการใช้งาน C ++ ที่คุณใช้ ตรวจสอบเอกสารประกอบ สำหรับคำถามอื่น ๆ : "ถ้าไม่ใช่ทำไม" - มาตรฐาน C ++ ยังไม่ได้กล่าวถึงหัวข้อใด ๆ แต่เวอร์ชัน C ++ ที่กำลังมาถึงจะรับรู้เธรดและระบุอย่างชัดเจนว่าการกำหนดค่าเริ่มต้นของสแตติกแบบโลคัลคือ thread-safe หากสองเธรดเรียกใช้ฟังก์ชันดังกล่าวหนึ่งเธรดจะดำเนินการเตรียมใช้งานในขณะที่อีกเธรดจะบล็อกและรอให้การดำเนินการเสร็จสิ้น