Vra

Is daar'n manier om te implementeer'n singleton voorwerp in C++ wat is:

  1. Lui gebou in'n draad veilige wyse (twee drade kan gelyktydig word die eerste gebruiker van die singleton - dit moet nog net gebou word een keer).
  2. Nie staatmaak op statiese veranderlikes gebou vooraf (so die singleton voorwerp is self veilig om te gebruik tydens die konstruksie van statiese veranderlikes).

(Ek weet nie my C++ goed genoeg, maar is dit die geval dat integrale en konstante statiese veranderlikes is geïnisialiseer voordat enige kode uitgevoer word (dit wil sê, selfs voor statiese vervaardigerskampioenskap is uitgevoer - hul waardes dalk reeds "geïnisialiseer" in die program beeld)?As dit so is - miskien is dit kan uitgebuit word om te implementeer'n singleton mutex - wat op sy beurt gebruik word om te wag die skepping van die ware singleton..)


Uitstekende, dit blyk dat ek het'n paar goeie antwoorde nou (shame ek kan nie merk 2 of 3 as die antwoord).Daar blyk te wees twee breë oplossings:

  1. Gebruik statiese initialisation (in teenstelling met die dinamiese initialisation) van'n POD statiese veranderlike, en die uitvoering van my eie mutex met wat met behulp van die ingeboude atoom instruksies.Dit was die tipe van die oplossing was ek sinspeel op my vraag, en ek glo ek reeds geweet het.
  2. Gebruik'n ander biblioteek funksie soos pthread_once of hupstoot::call_once.Hierdie ek het beslis nie geweet het nie oor - en ek is baie dankbaar vir die antwoorde geplaas.
Was dit nuttig?

Oplossing

Basies, jy vra vir gesinchroniseer skepping van'n singleton, sonder die gebruik van enige sinchronisasie (voorheen-gebou veranderlikes).In die algemeen, nee, dit is nie moontlik nie.Wat jy nodig het om iets beskikbaar vir sinchronisasie.

Soos vir jou ander vraag, ja, statiese veranderlikes wat kan staties wees geïnisialiseer (d. w. sgeen runtime kode nodig) is gewaarborg om te wees geïnisialiseer voor ander kode uitgevoer word.Dit maak dit moontlik om te gebruik om'n staties-geïnisialiseer mutex te sinchroniseer skepping van die singleton.

Van die 2003 hersiening van die C++ standaard:

Voorwerpe met statiese stoor duur (3.7.1) sal nul wees-geïnisialiseer (8.5) voor enige ander inisialisering plaasvind.Nul-inisialisering en inisialisering met'n konstante uitdrukking is gesamentlik genoem statiese inisialisering;al die ander inisialisering is'n dinamiese inisialisering.Voorwerpe van die POD tipes (3.9) met statiese stoor duur geïnisialiseer met konstante uitdrukkings (5.19) sal wees geïnisialiseer voordat enige dinamiese inisialisering plaasvind.Voorwerpe met statiese stoor duur gedefinieer in namespace omvang in die dieselfde vertaling eenheid en dinamiese geïnisialiseer sal wees geïnisialiseer in die volgorde waarin hulle definisie verskyn in die vertaling eenheid.

As jy weet dat jy sal gebruik word om hierdie singleton tydens die inisialisering van ander statiese voorwerpe, ek dink jy sal vind dat sinchronisasie is'n non-issue.Na die beste van my kennis, al die groot opstellers inisialiseer statiese voorwerpe in'n enkele draad, so draad-veiligheid tydens statiese inisialisering.Jy kan verklaar jou singleton wyser te word NIETIG, en dan kyk om te sien as dit was geïnisialiseer voor jy dit gebruik.

Egter, dit word aanvaar dat jy weet wat sal jy gebruik hierdie singleton tydens statiese inisialisering.Dit is ook nie gewaarborg nie deur die standaard, so as jy wil om te wees heeltemal veilig te wees, gebruik'n staties-geïnisialiseer mutex.

Edit:Chris se voorstel om te gebruik'n atoom vergelyk-en-ruil sal beslis werk.As oordraagbaarheid is nie'n probleem (en die skep van bykomende tydelike singletons is nie'n probleem nie), dan is dit'n effens laer oorhoofse oplossing.

Ander wenke

Ongelukkig Matt se antwoord funksies wat genoem dubbel gekontroleer sluit wat nie ondersteun word deur die C / C ++ geheue model. (Dit word ondersteun deur die Java 1.5 and later - en ek dink NET -. Geheue model) Dit beteken dat tussen die tyd toe die pObj == NULL tjek plaasvind en wanneer die slot (Mutex) verkry word, pObj dalk reeds toegeken op 'n ander draad. Draad skakel gebeur wanneer die OS wil dit aan, nie tussen "lyne" van 'n program (wat geen betekenis post-samestelling in die meeste tale het).

Verder as Matt erken, gebruik hy 'n int as 'n slot eerder as 'n OS primitief. Moenie dit doen nie. Behoorlike slotte vereis dat die gebruik van geheue versperring instruksies, potensieel kas-lyn gloede, en so meer; gebruik primitiewes jou bedryfstelsel vir locking. Dit is veral belangrik omdat die primitiewes gebruik kan verander tussen die individuele CPU lyne wat jou bedryfstelsel loop op; wat werk op 'n CPU Foo kan nie werk op CPU Foo2. Die meeste bedryfstelsels óf native ondersteun POSIX drade (pthreads) of hulle as wrapper vir die OS threading pakket, so dit is dikwels die beste om te illustreer voorbeelde te gebruik.

As jou bedryfstelsel bied toepaslike primitiewes, en as jy absoluut nodig het vir prestasie, in plaas van om hierdie tipe van locking / inisialisering jy kan gebruik om 'n atoom vergelyk en ruil operasie om inisialiseer 'n gedeelde globale veranderlike. In wese, wat jy skryf sal lyk:

MySingleton *MySingleton::GetSingleton() {
    if (pObj == NULL) {
        // create a temporary instance of the singleton
        MySingleton *temp = new MySingleton();
        if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
            // if the swap didn't take place, delete the temporary instance
            delete temp;
        }
    }

    return pObj;
}

Dit werk net as dit veilig is om verskeie gevalle van jou Singleton (een per draad wat gebeur met GetSingleton () gelyktydig te roep) te skep, en dan gooi ekstras weg. Die OSAtomicCompareAndSwapPtrBarrier funksie wat op Mac OS X - die meeste bedryfstelsels bied 'n soortgelyke primitief - tjeks of pObj is NULL en net eintlik sit dit aan temp om dit as dit is. Dit maak gebruik van hardeware ondersteuning om werklik, letterlik net die uitruil weer uit te voer en te sê of dit gebeur het.

Nog 'n fasiliteit te benut as jou OS bied dit wat in tussen hierdie twee uiterstes is pthread_once. Dit laat jou opstel van 'n funksie wat slegs een keer is hardloop - basies deur al die sluiting / versperring / ens te doen. kullery vir jou -. maak nie saak hoeveel keer dit drie maande of op hoeveel drade dit opgeroep

Hier is 'n baie eenvoudige lui gebou Singleton lucky:

Singleton *Singleton::self() {
    static Singleton instance;
    return &instance;
}

Dit is lui, en die volgende C ++ standaard (C ++ 0x) vereis dat dit veilig draad wees. Trouens, ek glo dat ten minste g ++ implemente hierdie in 'n draad veilige manier. So as dit jou doel samesteller of as jy 'n samesteller wat ook implemente hierdie in 'n draad veilige manier (dalk nuwer Visual Studio opstellers doen? Ek weet nie) gebruik, dan is dit dalk al wat jy nodig het .

Sien ook http: // www. open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2513.html oor hierdie onderwerp.

Jy kan dit nie doen sonder enige statiese veranderlikes, maar as jy bereid is om te verdra een, kan jy gebruik Hupstoot te gee.Draad vir hierdie doel.Lees die "een-tyd initialisation" afdeling vir meer inligting.

Dan in jou singleton accessor funksie, gebruik boost::call_once te bou die voorwerp, en stuur dit terug.

Vir gcc, dit is nogal maklik:

LazyType* GetMyLazyGlobal() {
    static const LazyType* instance = new LazyType();
    return instance;
}

GCC sal seker maak dat die inisialisering is atoom. Vir VC ++, dit is nie die geval . : - (

Een groot probleem met hierdie meganisme is die gebrek aan toetsbaarheid: as jy nodig het om die LazyType herstel na 'n nuwe een tussen toetse, of wil die LazyType * verander na 'n MockLazyType *, sal jy nie in staat wees om. Gegewe hierdie, is dit gewoonlik die beste om 'n statiese Mutex + statiese wyser gebruik.

Ook, moontlik 'n eenkant: Dit is die beste om altyd vermy statiese nie-POD tipes. (Pointers te peule is OK.) Die redes hiervoor is baie: as jy praat, is inisialisering orde nie gedefinieer - nie die volgorde waarin destructors al genoem word. As gevolg hiervan, sal programme beland gekraak wanneer hulle probeer om af te sluit; dikwels nie 'n groot deal, maar soms 'n showstopper wanneer die profiler jy probeer om te gebruik vereis 'n skoon uitgang.

Hoewel hierdie vraag is reeds beantwoord, ek dink daar is 'n paar ander punte te noem:

  • As jy lui-Instantiëring van die Singleton wil terwyl die gebruik van 'n verwysing na 'n dinamiese toegeken byvoorbeeld jy het om seker te maak jy dit skoon te maak op die regte punt.
  • Jy kan oplossing Matt se gebruik, maar jy sal nodig het om 'n behoorlike Mutex / kritieke afdeling gebruik vir locking, en deur die nagaan van "pObj == NULL" beide voor en na die slot. Natuurlik, pObj sal ook moet wees statiese ,) . A Mutex sou onnodig swaar wees in hierdie geval, sal jy 'n beter gaan met 'n kritieke afdeling.

Maar soos reeds gesê, kan jy nie waarborg threadsafe lui-opstart sonder die gebruik van ten minste een sinchronisasie primitief.

Edit: Yup Derek, jy is reg. My fout. :)

  

Jy kan oplossing Matt se gebruik, maar jy sal nodig het om 'n behoorlike Mutex / kritieke afdeling gebruik vir locking, en deur die nagaan van "pObj == NULL" beide voor en na die slot. Natuurlik, pObj sou ook statiese te wees;). A Mutex sou onnodig swaar wees in hierdie geval, sal jy 'n beter gaan met 'n kritieke afdeling.

PB, dit nie werk nie. Soos Chris het daarop gewys, is dit dubbel-check sluiting, wat nie gewaarborg om te werk in die huidige C ++ standaard. Sien: C ++ en die gevare van dubbel gekontroleer Locking

Edit: Geen probleem, PB. Dit is regtig lekker in tale waar dit werk nie. Ek verwag dat dit sal werk in C ++ 0x (maar ek is nie seker), want dit is so 'n gerieflike idioom.

  1. lees oor swak geheue model. Dit kan dubbel gekontroleer slotte en spinlocks breek. Intel is sterk geheue model (nog), so op Intel dit makliker

  2. versigtig gebruik "vlugtige" om caching van dele te verhoed dat die voorwerp in registers, anders sal jy die voorwerp wyser het geïnisialiseer, maar nie die voorwerp self, en die ander draad sal crash

  3. die einde van statiese veranderlikes inisialisering versus gedeel kode laai is soms nie triviaal. Ek het gevalle gesien toe die kode om 'n voorwerp vernietig reeds gelaai is, so die program neergestort op uitgang

  4. sulke voorwerpe is moeilik om behoorlik te vernietig

In die algemeen single is moeilik om reg en moeilik om te ontfout doen. Dit is beter om hulle heeltemal te vermy.

Ek veronderstel sê dit nie doen nie, want dit is nie veilig en sal waarskynlik breek meer dikwels as net die initialiseren hierdie dinge in main() is nie van plan om te wees wat gewild.

(En ja, ek weet dat wat daarop dui dat beteken moet jy nie probeer om interessante dinge te doen in vervaardigerskampioenskap van globale voorwerpe. Dis die punt.)

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top