Meyers'in Singleton desen ipliği uygulaması güvenli midir?


145

Aşağıdaki uygulama, Singleton(Meyers 'Singleton) ipliğinin tembel başlatılmasını kullanarak güvenli midir?

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

Değilse, neden ve nasıl iplik güvenli hale getirilir?


Birisi bunun neden güvenli olmadığını açıklayabilir misiniz? Bağlantılarda sözü edilen makaleler, alternatif bir uygulama kullanarak bir iplik güvenliğini tartışmaktadır (bir işaretçi değişkeni, yani statik Singleton * özelliği kullanarak).
Ankur



Yanıtlar:


168

In C ++ 11 , bu iş parçacığı güvenlidir. Göre standart , §6.7 [stmt.dcl] p4:

Değişken başlatılırken kontrol bildirime aynı anda girerse, eşzamanlı yürütme başlatmanın tamamlanmasını bekleyecektir .

Özellik için GCC ve VS desteği ( MSDN'de Sihirli Statik olarak da bilinen Eşzamanlılık ile Dinamik Başlatma ve İmha ) aşağıdaki gibidir:

@Mankarse ve @olen_gam'a yorumları için teşekkürler.


In C ++ 03 , bu kod parçacığı güvenli değildi. Meyers tarafından "C ++ ve Çift Kontrollü Kilitlemenin Tehlikeleri" adlı bir makale vardır, bu da desenin iş parçacığı güvenli uygulamalarını tartışır ve sonuç, az çok, (C ++ 03'te) örnekleme yönteminin etrafında tam kilitlemedir. temel olarak tüm platformlarda uygun eşzamanlılığı sağlamanın en basit yoludur, ancak çift denetimli kilitleme deseni varyantlarının çoğu biçimi, stratejik olarak yerleştirilen bellek engelleriyle birlikte yerleştirilmedikçe belirli mimarilerdeki yarış koşullarından muzdarip olabilir .


3
Ayrıca Modern C ++ Tasarımında Alexandrescu'nun Singleton Kalıbı (ömür boyu ve iplik güvenliği) hakkında geniş bir tartışma var. Loki'nin sitesine bakınız: loki-lib.sourceforge.net/index.php?n=Pattern.Singleton
Matthieu M.

1
Boost :: call_once ile iş parçacığı açısından güvenli bir singleton oluşturabilirsiniz.
CashCow

1
Ne yazık ki, standardın bu kısmı Visual Studio 2012 C ++ Derleyicisi'nde uygulanmamıştır. Burada "C ++ 11 Temel Dil Özellikleri: Eşzamanlılık" tablosunda "
Sihirli Statikler

Standarttaki pasaj, inşaatı ele alır, ancak yıkımı değil. Standart, başka bir iş parçacığı program sonlandırıldığında ona erişmeye çalışırken (veya daha önce) nesnenin bir iş parçacığında yok olmasını engelliyor mu?
stewbasic

IANA (C ++ dili) L fakat bölüm 3.6.3 [basic.start.term] p2, nesneye zarar verildikten sonra erişmeye çalışarak tanımsız davranışlara çarpmanın mümkün olduğunu gösteriyor?
stewbasic

21

Neden threadsafe olmadığına dair sorunuzu cevaplamak için, ilk çağrının yapıcıyı çağırması instance()gerektiği için değil Singleton s. İş parçacığı güvenli olmak için bu kritik bir bölümde gerçekleşmelidir ve standartta kritik bir bölümün alınmasına gerek yoktur (bugüne kadar standart dişler üzerinde tamamen sessizdir). Derleyiciler bunu genellikle statik bir booleanın basit bir kontrolünü ve artışını kullanarak uygular - ancak kritik bir bölümde değil. Aşağıdaki sözde kod gibi bir şey:

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)));
}

İşte basit bir iş parçacığı için güvenli Singleton (Windows için). Biz derleyici otomatik olarak başlatmak zorunda böylece, Windows CRITICAL_SECTION nesnesi için basit bir sınıf sarmalayıcı kullanır CRITICAL_SECTIONönce main()denir. İdeal olarak, kritik bölüm tutulduğunda ortaya çıkabilecek istisnalarla başa çıkabilen gerçek bir RAII kritik bölüm sınıfı kullanılır, ancak bu, bu cevabın kapsamı dışındadır.

Temel işlem, bir örneği Singletonistendiğinde bir kilit alındığında, gerekiyorsa Singleton'ın oluşturulması, daha sonra kilidin serbest bırakılması ve Singleton referansının geri döndürülmesidir.

#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;
}

Adam - "daha iyi bir küresel olmak" için çok saçma.

(Bazı hataların geçmesine izin vermediysem) bu uygulamanın ana dezavantajları:

  • eğer new Singleton()atar, kilit serbest olmayacak. Bu, burada sahip olduğum basit yerine gerçek bir RAII kilit nesnesi kullanılarak düzeltilebilir. Bu, kilit için platformdan bağımsız bir sargı sağlamak için Boost gibi bir şey kullanırsanız, işleri taşınabilir hale getirmeye de yardımcı olabilir.
  • Bu, Singleton örneği main()çağrıldıktan sonra iş parçacığı güvenliğini garanti eder - daha önce çağırırsanız (statik nesnenin başlatılmasında olduğu gibi), başlatılmayabileceğinden işler çalışmayabilir CRITICAL_SECTION.
  • her örnek talep edildiğinde bir kilit alınmalıdır. Dediğim gibi, bu basit bir iş parçacığı güvenli bir uygulamadır. Daha iyi bir taneye ihtiyacınız varsa (veya çift kontrol kilidi tekniği gibi şeylerin neden kusurlu olduğunu bilmek istiyorsanız), Groo'nun cevabındaki bağlantılı makalelere bakın .

1
Ah oh. new Singleton()Atarsa ne olur ?
sbi

@Bob - adil olmak gerekirse, uygun bir kütüphane seti ile, kopyalanamazlık ve uygun bir RAII kilidi ile ilgili olan tüm rüzgârlar ortadan kalkar veya en az düzeyde olur. Ama örneğin makul derecede bağımsız olmasını istedim. Singleton, belki de minimum kazanç için çok fazla iş olsa da, onları küresellerin kullanımını yönetmede yararlı buldum. Nerede ve ne zaman kullanıldıklarını sadece bir adlandırma kuralından biraz daha iyi bulmayı kolaylaştırma eğilimindedirler.
Michael Burr

@sbi: bu örnekte, eğer new Singleton()atarsa, kilitte kesinlikle bir sorun vardır. lock_guardBoost gibi bir şey uygun bir RAII kilit sınıfı kullanılmalıdır . Örneğin az ya da çok bağımsız olmasını istedim ve zaten bir canavar gibiydi, bu yüzden istisna güvenliğini bıraktım (ama çağırdım). Belki bu kod bu yüzden uygunsuz bir yere cut-n-yapıştırılan almaz bunu düzeltmek gerekir.
Michael Burr

Neden singleton'u dinamik olarak ayırıyoruz? Neden sadece 'pInstance'ı' Singleton :: instance () 'in statik bir üyesi yapmıyorsunuz?
Martin York

@Martin - bitti. Haklısın, bu biraz daha basit - bir RAII kilit sınıfı kullansaydım daha da iyi olurdu.
Michael Burr

10

Bir sonraki standarda (bölüm 6.7.4) bakıldığında, statik lokal başlatmanın iş parçacığı açısından güvenli olduğunu açıklar. Dolayısıyla, standardın bu bölümü yaygın olarak uygulandığında, Meyer'in Singleton'u tercih edilen uygulama olacaktır.

Zaten birçok cevaba katılmıyorum. Çoğu derleyici bu şekilde statik başlatmayı zaten uygular. Dikkate değer bir istisna Microsoft Visual Studio'dur.


6

Doğru cevap derleyicinize bağlıdır. İplik güvenli yapmaya karar verebilir ; "doğal olarak" threadsafe değil.


5

Aşağıdaki uygulama [...] iş parçacığı güvenli midir?

Çoğu platformda, bu iş parçacığı için güvenli değildir. (C ++ standardının iş parçacıkları hakkında bilgi sahibi olmadığını açıklayan olağan feragatnameyi ekleyin, bu nedenle, yasal olarak, olup olmadığını söylemez.)

Değilse, neden [...]?

Bunun nedeni, hiçbir şeyin birden fazla iş parçacığının aynı anda syapıcı çalıştırmasını engellememesidir .

nasıl iplik güvenli hale getirmek için?

Scott Meyers ve Andrei Alexandrescu'nun "C ++ ve Çift Kontrollü Kilitlemenin Tehlikeleri" , iplik korumalı singletonlar konusunda oldukça iyi bir incelemedir.


2

MSalters'ın dediği gibi: Kullandığınız C ++ uygulamasına bağlıdır. Dokümanları kontrol edin. Diğer soruya gelince: "Değilse, neden?" - C ++ standardı henüz dişler hakkında bir şey söylemiyor. Ancak yaklaşan C ++ sürümü iş parçacıklarının farkındadır ve statik yerel ayarların başlatılmasının iş parçacığı açısından güvenli olduğunu açıkça belirtir. İki iş parçacığı böyle bir işlevi çağırırsa, bir iş parçacığı bir başlatma işlemi gerçekleştirir, diğer iş parçacığı bunu engeller ve bitmesini bekler.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.