Zastoji in livelocks - Kako se v resničnem svetu izogniti sočasnosti?

Zastoji se lahko pojavijo samo v sočasnih (več-navojnih) programih, kjer niti sinhronizirajo (uporabljajo zaklepe) dostop do enega ali več skupnih virov (spremenljivke in objekt) ali do navodil (kritični odsek).

Livelocks se pojavijo, ko se poskušamo izogniti zastojem z asinhronim zaklepanjem, pri čemer več niti konkurira za isto nastavljeno ključavnico, izogibajmo se ključavnicam, ki omogočajo, da drugi niti najprej gredo s ključavnico in na koncu nikoli ne pridobijo zakleni in nadaljuj; povzroča stradanje. Glejte spodaj, če želite razumeti, kako je zaklepanje aysnc, ki je strategija izogibanja zastoju, razlog za Livelock

Tu je nekaj teoretičnih rešitev Deadlocks, ena od njih (druga) pa je glavni razlog Livelocks-a

Teoretični pristopi

Ne uporabljajte ključavnic

Ni mogoče, kadar je treba sinhronizirati dve operaciji, na primer preprost bančni nakazilo, pri katerem obremenite en račun, preden lahko kreditite drug račun, in ne pustite, da se nobena druga nit dotika stanja v računih, dokler se ne opravi trenutna nit.

Ne blokirajte ključavnic, če nit ne more pridobiti ključavnice, sprostite prej pridobljene ključavnice, da poskusite pozneje znova.

Težaven za izvajanje in lahko povzroči stradanje (Livelocks), kjer nit vedno pušča ključavnice, da bi poskusili znova in naredili isto. Tudi ta pristop ima lahko prekomerne glave pri pogostih preklopih konteksta niti, kar zmanjšuje splošno delovanje sistema. Prav tako ni mogoče, da bi načrtovalski procesor izvajal pošteno, saj ne ve, katera nit dejansko čaka na zaklepanje.

Naj niti vedno zahtevajo ključavnice v strogem vrstnem redu

Na primer lažje kot narediti. Če pišemo funkcijo za prenos denarja z računa A v B, lahko zapišemo nekaj podobnega

// ob prevajalnem času zaklenemo prvi arg, nato drugi
javni neveljavni prenos (račun A, račun B, dolg denar) {
  sinhronizirano (A) {
    sinhronizirano (B) {
      A.add (znesek);
      B. ekstrakt (količina);
    }
  }
}
// med izvajanjem ne moremo slediti, kako se bodo imenovale naše metode
javna void run () {
  nova nit (() -> ta.transfer (X, Y, 10000)). start ();
  nova nit (() -> ta.transfer (Y, X, 10000)). start ();
}
// ta zagon () bo ustvaril zastoj
// prva nit se zaklene na X, počaka Y
// druga nit se zaklene na Y, počaka X

Resnična rešitev

Lahko kombiniramo pristope urejanja zaklepanja in časovnih ključavnic, da dobimo resnično besedno rešitev

Poslovno določeno naročanje ključavnic

Naš pristop lahko izboljšamo z razlikovanjem med A in B, na podlagi čigar številke računa je večja ali manjša.

// ob času izvajanja najprej upoštevamo manjši id
javni neveljavni prenos (račun A, račun B, dolg denar) {
  končni račun najprej = A.id 
  sinhronizirano (prvo) {
    sinhroniziran (drugi) {
      first.add (znesek);
      drugi odvzem (količina);
    }
  }
}
// med izvajanjem ne moremo slediti, kako se bodo imenovale naše metode
javna void run () {
  nova nit (() -> ta.transfer (X, Y, 10000)). start ();
  nova nit (() -> ta.transfer (Y, X, 10000)). start ();
}

Na primer, če sta X.id = 1111 in Y.id = 2222, ker najprej upoštevamo kot račun z manjšim ID-jem računa, zaporedje zaklepanja za izvedbe prenosa (Y, X, 10000) in prenosa (X, Y, 10000) bo isto. Ker ima številka računa manjšo od Y, bosta oba niti poskušala zakleniti X pred Y in le ena od njih bo uspela in nadaljevala z zaklepanjem zaključka Y in sprostitev ključavnic na X in Y, preden drugi niti pridobijo ključavnice in lahko nadaljujejo.

Časovno določeno s časom Počakajte, da poskusite zakleniti / zapreti zahteve za zaklepanje / asinhcijo

Rešitev uporabe poslovno določenega zapore ključavnice deluje le, če za asociativne odnose, kjer logika na enem mestu (….), Na primer v naši metodi, določa usklajevanje virov.

Morda imamo na voljo druge metode / logiko, ki se zaključi z uporabo naročilne logike, ki ni združljiva s prenosom (…). Da bi se izognili zastoju v takih primerih, je priporočljivo uporabiti zaklepanje async, kjer poskušamo zakleniti vir za končni / realistični čas (največji čas transakcije) + majhen naključni čas čakanja, tako da vse niti ne poskusijo znova pridobiti prezgodaj in ne vse hkrati, torej se izogibajte Livelockom (stradanje zaradi neresničnih poskusov pridobitve ključavnic)

// predpostavimo, da račun # getLock () omogoča zaklepanje računa (java.util.concurrent.locks.Lock)
// Račun lahko zaklene ključavnico, zagotovi lock () / unlock ()
javni dolgi getWait () {
/// vrne drsno povprečje časov prenosa za zadnji n prenos + majhna naključna sol v miljah, tako da se vse niti, ki čakajo na zaklepanje, ne prebudijo hkrati.
}
javni neveljaven prenos (Lock lockF, Lock lockS, int znesek) {
  končni račun najprej = A.id 
  boolean done = false;
  narediti {
    poskusi {
      poskusi {
        če (lockF.tryLock (getWait (), MILLISECONDS)) {
          poskusi {
            če (lockS.tryLock (getWait (), MILLISECONDS)) {
              končano = res;
            }
          } končno {
            lockS.unlock ();
          }
        }
      } ulov (InterruptException e) {
        vrgel novo RuntimeException ("Preklicano");
      }
    } končno {
      lockF.unlock ();
    }
  } medtem ko (! storjeno);

}
// med izvajanjem ne moremo slediti, kako se bodo imenovale naše metode
javna void run () {
    nova nit (() -> ta.transfer (X, Y, 10000)). start ();
    nova nit (() -> ta.transfer (Y, X, 10000)). start ();
}