การใช้งานเธรดรูปแบบซิงเกิลของ Meyers ปลอดภัยหรือไม่


145

การใช้งานต่อไปนี้โดยใช้การเริ่มต้นขี้เกียจของSingletonเธรด (Meyers 'Singleton) ปลอดภัยหรือไม่

static Singleton& instance()
{
     static Singleton s;
     return s;
}

ถ้าไม่ทำไมและวิธีที่จะทำให้มันปลอดภัยไหม?


ใครช่วยได้โปรดอธิบายว่าทำไมสิ่งนี้ถึงไม่ปลอดภัย บทความที่กล่าวถึงในลิงค์จะกล่าวถึงความปลอดภัยของเธรดโดยใช้ทางเลือกอื่น (โดยใช้ตัวแปรพอยน์เตอร์เช่น Singleton * pInstance)
Ankur



คำตอบ:


168

ในC ++ 11เป็นเธรดที่ปลอดภัย ตามที่มาตรฐาน , §6.7 [stmt.dcl] p4:

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

รองรับ GCC และ VS สำหรับคุณสมบัติ (การกำหนดค่าเริ่มต้นและการทำลายแบบไดนามิกด้วยการทำงานพร้อมกันหรือที่เรียกว่าMagic Statics บน MSDN ) มีดังนี้:

  • Visual Studio: รองรับตั้งแต่Visual Studio 2015
  • GCC: รองรับตั้งแต่GCC 4.3

ขอบคุณ @Mankarse และ @olen_gam สำหรับความคิดเห็นของพวกเขา


ในC ++ 03รหัสนี้ไม่ปลอดภัยสำหรับเธรด มีบทความโดย Meyers เรียกว่า"C ++ และ Perils of Double-Checked Locking"ซึ่งกล่าวถึงการใช้งานรูปแบบที่ปลอดภัยของเธรดและข้อสรุปคือมากหรือน้อยนั้น (ใน C ++ 03) การล็อคเต็มรอบวิธีการสร้างอินสแตนซ์ โดยทั่วไปแล้วเป็นวิธีที่ง่ายที่สุดในการตรวจสอบความพร้อมกันที่เหมาะสมบนแพลตฟอร์มทั้งหมดในขณะที่รูปแบบส่วนใหญ่ของรูปแบบการล็อคแบบตรวจสอบอีกครั้งอาจได้รับผลกระทบจากสภาพการแข่งขันในสถาปัตยกรรมบางประเภทยกเว้นว่าคำแนะนำ


3
นอกจากนี้ยังมีการอภิปรายอย่างกว้างขวางเกี่ยวกับรูปแบบซิงเกิล (อายุการใช้งานและความปลอดภัยของเธรด) โดย Alexandrescu ในการออกแบบ Modern C ++ ดูเว็บไซต์ของ Loki: loki-lib.sourceforge.net/index.php?n=Pattern.Singleton
Matthieu M.

1
คุณสามารถสร้าง singleton safe-thread ได้ด้วย boost :: call_once
CashCow

1
น่าเสียดายที่ส่วนนี้ของมาตรฐานไม่ได้ถูกนำไปใช้ในคอมไพเลอร์ Visual Studio 2012 C ++ เรียกว่า "Magic Statics" ในตาราง "C ++ 11 Core Language Features: Concurrency" ตารางที่นี่: msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx
olen_garn

ตัวอย่างจากการก่อสร้างที่อยู่มาตรฐาน แต่ไม่ทำลาย มาตรฐานป้องกันไม่ให้วัตถุถูกทำลายบนเธรดหนึ่งในขณะที่ (หรือก่อนหน้า) เธรดอื่นพยายามเข้าถึงเมื่อสิ้นสุดโปรแกรมหรือไม่?
stewbasic

IANA (ภาษา C ++) L แต่ตอนที่ 3.6.3 [basic.start.term] p2 แนะนำว่าเป็นไปได้หรือไม่ที่จะมีพฤติกรรมที่ไม่ได้กำหนดโดยพยายามเข้าถึงวัตถุหลังจากที่มันถูกทำลายไปแล้ว?
stewbasic

21

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

1
เอ่อโอ้. จะเกิดอะไรขึ้นถ้าnew Singleton()พ่น
sbi

@Bob - เพื่อความเป็นธรรมด้วยชุดของไลบรารีที่เหมาะสม cruft ทั้งหมดที่เกี่ยวข้องกับการไม่คัดลอกและล็อค RAII ที่เหมาะสมจะหายไปหรือน้อยที่สุด แต่ฉันต้องการตัวอย่างให้มีเหตุผลในตัวเองอย่างสมเหตุสมผล แม้ว่าซิงเกิลตันนั้นมีงานจำนวนมากเพื่อให้ได้ผลประโยชน์น้อยที่สุด แต่ฉันก็พบว่ามันมีประโยชน์ในการจัดการการใช้งานของดาราจักร พวกเขามักจะทำให้ง่ายต่อการค้นหาว่าที่ไหนและเมื่อใดที่พวกเขาใช้ดีกว่าเพียงแค่แบบแผนการตั้งชื่อ
Michael Burr

@sbi: ในตัวอย่างนี้ถ้าnew Singleton()มีปัญหาเกิดขึ้นกับล็อค ควรใช้คลาสล็อค RAII ที่เหมาะสมซึ่งคล้ายกับlock_guardBoost ฉันต้องการตัวอย่างที่จะมีอยู่ในตัวเองมากขึ้นหรือน้อยลงและมันก็เป็นสัตว์ประหลาดอยู่เล็กน้อยดังนั้นฉันจึงออกจากความปลอดภัยยกเว้น (แต่เรียกมันออกมา) บางทีฉันควรแก้ไขเพื่อให้รหัสนี้ไม่ได้ถูกตัดวางที่อื่นที่ไม่เหมาะสม
Michael Burr

ทำไมจัดสรรซิงเกิลแบบไดนามิก? ทำไมไม่ทำให้ 'pInstance' เป็นสมาชิกแบบคงที่ของ 'Singleton :: instance ()'?
Martin York

@ มาร์ติน - เสร็จแล้ว คุณพูดถูกมันง่ายกว่านิดหน่อย - จะดีกว่านี้ถ้าฉันใช้คลาสล็อค RAII
Michael Burr

10

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

ฉันไม่เห็นด้วยกับคำตอบมากมายแล้ว คอมไพเลอร์ส่วนใหญ่ใช้การเริ่มต้นคงที่ด้วยวิธีนี้ ข้อยกเว้นหนึ่งที่น่าสังเกตคือ Microsoft Visual Studio


6

คำตอบที่ถูกต้องขึ้นอยู่กับคอมไพเลอร์ของคุณ มันสามารถตัดสินใจที่จะทำให้มันปลอดภัย มันไม่ใช่ threadsafe "naturallly"


5

การใช้งานต่อไปนี้ [... ] เธรดปลอดภัยหรือไม่

บนแพลตฟอร์มส่วนใหญ่สิ่งนี้จะไม่ปลอดภัยสำหรับเธรด (ผนวกข้อจำกัดความรับผิดชอบตามปกติอธิบายว่ามาตรฐาน C ++ ไม่รู้เกี่ยวกับเธรดดังนั้นตามกฎหมายมันไม่ได้บอกว่ามันเป็นหรือไม่)

ถ้าไม่ใช่ทำไม [... ]?

เหตุผลก็คือไม่มีสิ่งใดที่ป้องกันไม่ให้เธรดมากกว่าหนึ่งเธรดดำเนินการตัวsสร้าง ' พร้อมกัน

วิธีที่จะทำให้มันปลอดภัยไหม?

"C ++ และภัยคุกคามจากการล็อคเช็คที่สองครั้ง"โดย Scott Meyers และ Andrei Alexandrescu เป็นบทความที่ค่อนข้างดีในเรื่องของ singletons ที่ปลอดภัยสำหรับเธรด


2

ดังที่ MSalters กล่าวว่า: ขึ้นอยู่กับการใช้งาน C ++ ที่คุณใช้ ตรวจสอบเอกสารประกอบ สำหรับคำถามอื่น ๆ : "ถ้าไม่ใช่ทำไม" - มาตรฐาน C ++ ยังไม่ได้กล่าวถึงหัวข้อใด ๆ แต่เวอร์ชัน C ++ ที่กำลังมาถึงจะรับรู้เธรดและระบุอย่างชัดเจนว่าการกำหนดค่าเริ่มต้นของสแตติกแบบโลคัลคือ thread-safe หากสองเธรดเรียกใช้ฟังก์ชันดังกล่าวหนึ่งเธรดจะดำเนินการเตรียมใช้งานในขณะที่อีกเธรดจะบล็อกและรอให้การดำเนินการเสร็จสิ้น

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