Tarkista esto

Kokeneet kirjoittajat eivät ole vielä tarkistaneet sivun nykyistä versiota, ja se voi poiketa merkittävästi 20. syyskuuta 2017 tarkistetusta versiosta . tarkastukset vaativat 7 muokkausta .
Tarkista esto
Kaksoistarkistettu lukitus
Kuvattu suunnittelukuvioissa Ei

Kaksoistarkistettu lukitus on rinnakkainen suunnittelukuvio, joka on  suunniteltu vähentämään lukon hankkimiseen liittyviä ylimääräisiä kustannuksia. Ensin estotila tarkistetaan ilman synkronointia; lanka yrittää hankkia lukon vain, jos tarkastuksen tulos osoittaa, että sen on hankittava lukko.

Joillakin kielillä ja/tai joillakin koneilla tätä mallia ei ole mahdollista toteuttaa turvallisesti. Siksi sitä kutsutaan joskus anti-kuvioksi . Tällaiset ominaisuudet ovat johtaneet tiukkaan järjestyksen " tapahtuu ennen " -suhteeseen Java-muistimallissa ja C++-muistimallissa.

Sitä käytetään yleisesti vähentämään laiskan alustuksen toteuttamista monisäikeisissä ohjelmissa, kuten osana Singleton-suunnittelumallia . Laisalla muuttujan alustuksella alustus viivästyy, kunnes muuttujan arvoa tarvitaan laskennassa.

Java -esimerkki

Harkitse seuraavaa Java -koodia, joka on otettu kohteesta [1] :

// Yksisäikeinen versio luokka Foo { private Helper helper = null ; public Helper getHelper () { if ( helper == null ) helper = new Helper (); paluuauttaja ; _ } // ja muut luokan jäsenet... }

Tämä koodi ei toimi oikein monisäikeisessä ohjelmassa. Menetelmän getHelper()on hankittava lukko, jos sitä kutsutaan samanaikaisesti kahdesta säikeestä. Itse asiassa, jos kenttää helperei ole vielä alustettu ja kaksi säiettä kutsuvat menetelmää samanaikaisesti getHelper(), molemmat säikeet yrittävät luoda objektin, mikä johtaa ylimääräisen objektin luomiseen. Tämä ongelma ratkaistaan ​​käyttämällä synkronointia seuraavan esimerkin mukaisesti.

// Oikea, mutta "kallis" monisäikeinen versio class Foo { private Helper helper = null ; julkinen synkronoitu Apulainen getHelper ( ) { if ( helper == null ) helper = new Helper (); paluuauttaja ; _ } // ja muut luokan jäsenet... }

Tämä koodi toimii, mutta se tuo ylimääräisiä synkronointikustannuksia. Ensimmäinen kutsu getHelper()luo objektin, ja vain muutamat säikeet, joita kutsutaan getHelper()objektin alustuksen aikana, on synkronoitava. Kun synkronointi on alustettu, puhelun synkronointi getHelper()on redundantti, koska se lukee vain muuttujan. Koska synkronointi voi vähentää suorituskykyä kertoimella 100 tai enemmän, lukitseminen joka kerta, kun tätä menetelmää kutsutaan, näyttää tarpeettomalta: kun alustus on valmis, lukkoa ei enää tarvita. Monet ohjelmoijat ovat yrittäneet optimoida tämän koodin seuraavasti:

  1. Ensin se tarkistaa, onko muuttuja alustettu (ilman lukkoa). Jos se alustetaan, sen arvo palautetaan välittömästi.
  2. Lukon hankkiminen.
  3. Se tarkistaa uudelleen, onko muuttuja alustettu, koska on täysin mahdollista, että ensimmäisen tarkistuksen jälkeen toinen säie alustaa muuttujan. Jos se alustetaan, sen arvo palautetaan.
  4. Muussa tapauksessa muuttuja alustetaan ja palautetaan.
// Virheellinen (Symantec JIT- ja Java-versioissa 1.4 ja aiemmissa) monisäikeinen versio // "Double-Checked Locking" -malliluokka Foo { private Helper helper = null ; public Helper getHelper () { if ( apulainen == null ) { synkronoitu ( tämä ) { if ( apulainen == null ) { auttaja = new Helper (); } } } paluu apulainen ; } // ja muut luokan jäsenet... }

Intuitiivisella tasolla tämä koodi näyttää oikealta. On kuitenkin joitain ongelmia (Java 1.4:ssä ja aiemmissa versioissa sekä epästandardeissa JRE-toteutuksissa), joita pitäisi ehkä välttää. Kuvittele, että tapahtumat monisäikeisessä ohjelmassa etenevät seuraavasti:

  1. Säie A huomaa, että muuttujaa ei ole alustettu, hankkii sitten lukon ja aloittaa alustuksen.
  2. Joidenkin ohjelmointikielten semantiikka[ mitä? ] on sellainen, että säiettä A saa antaa viittauksen alustusvaiheessa olevaan objektiin jaetulle muuttujalle (joka yleensä rikkoo melko selkeästi kausaalista yhteyttä, koska ohjelmoija pyysi melko selvästi viittausta objekti muuttujaan [eli julkaista viittaus jaettuna] - hetkellä alustuksen jälkeen , ei hetkellä ennen alustusta).
  3. Säie B huomaa, että muuttuja on alustettu (ainakin se luulee niin) ja palauttaa muuttujan arvon ilman lukitusta. Jos säie B käyttää nyt muuttujaa ennen kuin säiettä A on alustettu, ohjelman toiminta on virheellinen.

Yksi kaksoistarkistetun lukituksen käytön vaaroista J2SE 1.4 :ssä (ja aikaisemmissa) on, että ohjelma näyttää usein toimivan oikein. Ensinnäkin kyseessä oleva tilanne ei toistu kovin usein; toiseksi on vaikea erottaa tämän mallin oikea toteutus siitä, jolla on kuvattu ongelma. Riippuen kääntäjästä , ajastimen prosessoriajan kohdistamisesta säikeille ja muiden käynnissä olevien samanaikaisten prosessien luonteesta, kaksinkertaisesti tarkastetun lukituksen virheellisestä toteutuksesta aiheutuvia virheitä esiintyy yleensä sattumanvaraisesti. Tällaisten virheiden toistaminen on yleensä vaikeaa.

Voit ratkaista ongelman käyttämällä J2SE 5.0 :aa . Uusi avainsanasemantiikka volatilemahdollistaa muuttujaan kirjoittamisen oikein tässä tapauksessa. Tämä uusi malli on kuvattu kohdassa [1] :

// Toimii uuden haihtuvan semantiikan kanssa // Ei toimi Java 1.4:ssä ja aiemmissa versioissa haihtuvan semantiikan luokan Foo vuoksi { yksityinen haihtuva Helper helper = null ; public Helper getHelper () { if ( helper == null ) { synkronoitu ( this ) { if ( helper == null ) helper = new Helper (); } } paluuta avustaja ; } // ja muut luokan jäsenet... }

On ehdotettu monia kaksinkertaisesti tarkistettuja lukitusvaihtoehtoja, jotka eivät nimenomaisesti (haihtuvien tai synkronoinnin kautta) osoita, että objekti on täysin rakennettu, ja ne kaikki ovat virheellisiä Symantec JIT:lle ja vanhoille Oracle JRE:ille [2] [3] .

Käyttöesimerkki C# :ssa

julkinen suljettu luokka Singleton { yksityinen yksittäinen ( ) { // alusta uusi objektiinstanssi } yksityinen staattinen haihtuva Singleton singletonInstance ; yksityinen staattinen vain luku -objekti SyncRoot = uusi Objekti (); public static Singleton GetInstance () { // onko objekti luotu if ( singletonInstance == null ) { // ei, ei luotu // vain yksi säie voi luoda sen lock ( syncRoot ) { // tarkista onko toinen säie luonut objekti if ( singletonInstance == null ) { // ei, ei luonut sitä - luo singletonInstance = new Singleton (); } } } return singletonInstance ; } }

Microsoft vahvistaa [4] , että haihtuvaa avainsanaa käytettäessä on turvallista käyttää Double Checked -lukituskuviota.

Esimerkki Pythonin käytöstä

Seuraava Python - koodi näyttää esimerkkitoteutuksen laiskasta alustuksesta yhdessä kaksoistarkistetun lukitusmallin kanssa:

# vaativat Python2 tai Python3 #-*- koodauksen: UTF-8 *-* tuonti kierteitys luokka SimpleLazyProxy : '''laiska objektin alustus lankaturvallinen''' def __init__ ( itse , tehdas ): itse . __lock = ketjuttaminen . RLukitse () itse . __obj = Ei itse . __tehdas = tehdas def __call__ ( self ): ''''toiminto todellisen objektin käyttämiseksi jos objektia ei luoda, se luodaan''' # yritä saada "nopea" pääsy objektiin: obj = self . __obj jos obj ei ole Ei mitään : # onnistui! return obj else : # objektia ei ehkä ole vielä luotu itsensä kanssa . __lock : # päästä kohteeseen eksklusiivisessa tilassa: obj = self . __obj jos obj ei ole None : # osoittaa, että objekti on jo luotu. #älä luo sitä uudelleen return obj else : # objektia ei ole vielä oikein luotu. # luodaan se! obj = itse . __tehdas () itse . __obj = obj palauttaa obj __getattr__ = lambda itse , nimi : \ getattr ( itse (), nimi ) def lazy ( proxy_cls = SimpleLazyProxy ): '''sisustin, joka muuttaa luokan luokaksi laiskalla alustuksella välityspalvelinluokan avulla''' class ClassDecorator : def __init__ ( self , cls ): # sisustajan alustus, # mutta ei koristeltua luokkaa eikä välityspalvelinluokkaa itse . cls = cls def __call__ ( self , * args , ** kwargs ): # kutsu välityspalvelinluokan alustukseen # välitä tarvittavat parametrit välityspalvelinluokalle # alustaaksesi koristettavan luokan return proxy_cls ( lambda : itse . cls ( * args , ** kwargs )) palauta ClassDecorator # yksinkertainen tarkistus: def testi_0 (): tulosta ( ' \t\t\t *** Testin aloitus ***' ) tuontiaika _ @lazy () # tämän luokan esiintymiä ovat laiska-alustettu luokka TestType : def __init__ ( itse , nimi ): print ( ' %s : Luotu...' % name ) # lisää keinotekoisesti objektin luontiaikaa # lisätäksesi säikeen kilpailua aika . nukkua ( 3 ) itse . nimi = nimi print ( ' %s : Luotu ' % name ) def test ( self ): print ( ' %s : Testataan' % self . name ) # yksi tällainen ilmentymä on vuorovaikutuksessa useiden säikeiden kanssa test_obj = TestType ( 'Säikeiden välinen testiobjekti' ) kohdetapahtuma = ketjuttaminen . Tapahtuma () def threads_target (): # toiminto, jonka säikeet suorittavat: # odota erityistä tapahtumaa target_event . odota () # heti tämän tapahtuman tapahtuessa - # kaikki 10 säiettä pääsevät samanaikaisesti testiobjektiin # ja tällä hetkellä se alustetaan johonkin säikeistä test_obj . testi () # luo nämä 10 säiettä yllä olevalla algoritmilla threads_target() threads = [ ] säikeelle alueella ( 10 ): thread = threading . Säie ( kohde = thread_target ) lanka . aloita () säikeet . liittää ( lanka ) print ( 'Objektiin ei ole tähän mennessä ollut pääsyä' ) # odota vähän... aika . nukkua ( 3 ) # ...ja suorita test_obj.test() samanaikaisesti kaikissa säikeissä print ( 'Sammuta tapahtuma käyttää testiobjektia!' ) target_event . asettaa () # langan loppu säikeissä : lanka . _ liity () tulosta ( ' \t\t\t *** Testin loppu ***' )

Linkit

Muistiinpanot

  1. David Bacon, Joshua Bloch ja muut. "Kaksoistarkistettu lukitus on rikki" -ilmoitus . Bill Pugh -verkkosivusto. Arkistoitu alkuperäisestä 1. maaliskuuta 2012.