Kinni- ja otsevallukud - kuidas vältida reaalses maailmas samaaegsust?

Lukustusi võib esineda ainult samaaegsetes (mitme keermega) programmides, kus niidid sünkroonivad (kasutavad lukke) juurdepääsu ühele või mitmele jagatud ressursile (muutujad ja objekt) või käskude kogumile (kriitiline osa).

Otselukud tekivad siis, kui üritame asünkroonsel lukustamisel ummikseisu vältida, kui sama keeruka luku (te) pärast konkureerivad mitu lõime, vältige luku (de) hankimist, et teised niidid saaksid esimesena lukuga kaasas käia, ja lõpuks ei saa nad kunagi omandada lukku ja jätka; põhjustades nälga. Vaadake allpool, et mõista, kuidas ummikseisu vältimiseks mõeldud strateegia aysnc-lukustamine võib olla Livelocki põhjus

Siin on mõned ummikute teoreetilised lahendused ja üks neist (teine) on Livelocksi peamine põhjus

Teoreetilised lähenemised

Ärge kasutage lukke

Pole võimalik, kui tuleb sünkroonida kaks toimingut, näiteks lihtne pangaülekanne, kus ühelt kontolt debiteeritakse enne, kui saate teise konto krediteerida, ja mitte lasta ühelgi teisel lõimel puudutada kontode saldot, kuni praegune lõim on tehtud.

Ärge blokeerige lukke, kui niit ei saa lukku, peaks see vabastama varem omandatud lukud, et hiljem uuesti proovida

Tülikas rakendamine ja võib põhjustada nälgimist (Livelocks), kus niit laseb lukkudel minna vaid uuesti proovida ja sama teha. Samuti võib sellel lähenemisel olla keerukate kontekstivahetuste sagedane ületamine, vähendades süsteemi üldist jõudlust. Samuti ei saa mingil moel protsessori planeerijal rakenduse õiglust rakendada, kuna ta ei tea, milline niit on tegelikult kõige kauem lukku oodanud.

Laske niitidel alati lukud taotleda range järjekorra järgi

Näiteks lihtsamini öeldud kui tehtud. Kui kirjutame funktsiooni raha ülekandmiseks kontolt A punkti B, võime kirjutada midagi sellist

// kompileerimise ajal võetakse lukk esimesel argil, siis teisel
avalik tühine ülekanne (konto A, konto B, pikk raha) {
  sünkroonitud (A) {
    sünkroonitud (B) {
      A.add (summa);
      B.altrakt (summa);
    }
  }
}
// Runtime ajal ei saa me jälgida, kuidas meie meetodeid kutsutakse
avalik tühine käitamine () {
  uus niit (() -> see.transfeer (X, Y, 10000)). start ();
  uus niit (() -> see.transfeer (Y, X, 10000)). start ();
}
// see käivitus () loob ummikseisu
// esimene niit lukustub X-il, ootab Y-d
// teine ​​niit lukustub Y-l, ootab X-i

Reaalse maailma lahendus

Tõelise sõnalahenduse saamiseks saame ühendada lukkude tellimise ja ajastatud lukkude lähenemisviise

Ärikindla lukustuse tellimine

Saame oma lähenemisviisi parendada, eristades punkte A ja B selle põhjal, kelle kontonumber on suurem või väiksem.

// töö ajal võtame kõigepealt väiksema ID-ga konto lukustuse
avalik tühine ülekanne (konto A, konto B, pikk raha) {
  lõppkonto esimene = A.id 
  sünkroonitud (esimene) {
    sünkroonitud (teine) {
      esimene.add (summa);
      teine.lõige (summa);
    }
  }
}
// Runtime ajal ei saa me jälgida, kuidas meie meetodeid nimetatakse
avalik tühine käitamine () {
  uus niit (() -> see.transfeer (X, Y, 10000)). start ();
  uus niit (() -> see.transfeer (Y, X, 10000)). start ();
}

Näiteks kui X.id = 1111 ja Y.id = 2222, kuna me võtame esimese kontona väiksema konto ID-ga konto, siis ülekande (Y, X, 10000) ja ülekande (X, Y, 10000) on sama. Kui X-i kontonumber on väiksem kui Y, üritavad mõlemad lõimed X-i lukustada enne Y-d ja ainult üks neist õnnestub ning lukustab Y-viimistluse ja vabastab lukud X-il ja Y-l, enne kui teised lõimed lukud omandavad ja saavad edasi liikuda.

Ettevõtte määratud ajastatud ooteaeg tryLock / asynci lukustamistaotlused

Ettevõtte poolt määratud lukukorralduse kasutamise lahendus töötab ainult juhul, kui assotsieeruvad suhted, kus loogika ühes kohas üle kandub (….), Nagu meie meetod, määrab ressursside koordineerimise.

Võimalik, et meil on ka muid meetodeid / loogikat, mille lõpuks kasutatakse tellimisloogikat, mis ei ühildu ülekandega (…). Ummistuse vältimiseks on sellistel puhkudel soovitatav kasutada asünkroonilukustamist, kus proovime ressursi lukustada lõplikuks / realistlikuks ajaks (maks. Tehinguaeg) + väikeseks-juhuslikuks ooteajaks, nii et kõik lõimed ei prooviks uuesti omandage liiga vara ja mitte kõiki samal ajal, vältides seega Livelocks'i kasutamist (nälg, mis on tingitud elujõulisest lukkude omandamise katsest)

// eeldame, et konto nr getLock () annab meile konto lukustuse (java.util.concurrent.locks.Lock)
// Konto võiks kapseldada luku, pakkuda lukku () / avada ()
avalik pikk getWait () {
/// tagastab viimase n ülekande edastusaja liikuva keskmise + väike-juhuslik sool millistes, nii et kõik lukustamist ootavad niidid ei ärkaks samal ajal.
}
avalik tühine ülekanne (Lock lockF, Lock lockS, int summa) {
  lõppkonto esimene = A.id 
  tõeväärtus tehtud = vale;
  tee {
    proovige {
      proovige {
        if (lockF.tryLock (getWait (), MILLISECONDS)) {
          proovige {
            if (lockS.tryLock (getWait (), MILLISECONDS)) {
              tehtud = tõsi;
            }
          } lõpuks {
            lockS.unlock ();
          }
        }
      } saak (InterruptedException e) {
        viska uus RuntimeException ("Tühistatud");
      }
    } lõpuks {
      lockF.unlock ();
    }
  } while (! tehtud);

}
// Runtime ajal ei saa me jälgida, kuidas meie meetodeid kutsutakse
avalik tühine käitamine () {
    uus niit (() -> see.siire (X, Y, 10000)). start ();
    uus niit (() -> see.transfeer (Y, X, 10000)). start ();
}