Holtpontok és élőzárak - Hogyan kerüljük el a valós világban a párhuzamosságot?

A holtpontok csak olyan párhuzamos (többszálú) programokban fordulhatnak elő, ahol a szálak szinkronizálják (zárakat használnak) egy vagy több megosztott erőforráshoz (változók és objektumok) vagy utasításkészlethez (kritikus szakasz) való hozzáférést.

Az élőzárak akkor fordulnak elő, amikor megpróbáljuk elkerülni a holtpontokat aszinkron reteszeléssel, ahol több szál verseng ugyanazon beállított zár (ok) ra, elkerüljük a zár (ok) megszerzését, hogy más szálak előbb menjenek a zárral, és végül soha nem kapnak meg egy zár és folytassa; éhezést okoz. Lásd alább, hogy megértse, hogyan lehet az Aysnc-zárolás, amely stratégia a holtpontok elkerülésére, a Livelock okát

Íme néhány a holtpontok elméleti megoldása, és ezek egyike (a második) a Livelocks fő oka

Elméleti megközelítések

Ne használjon zárakat

Nem lehetséges, ha két műveletet szinkronizálni kell, például egy egyszerű banki átutalással, amikor az egyik számlát megterheljük, mielőtt jóváhagynánk a másik számlát, és ne engedjük, hogy más szálak érintsék meg a számlák egyenlegét, amíg az aktuális szál meg nem történik.

Ne blokkolja a zárakat, ha egy szál nem tudja megszerezni a zárat, akkor engedje fel a korábban megszerzett zárakat, hogy később újra megpróbálja

Bonyolult a megvalósítás, és éhezést okozhat (Livelocks), ahol egy szál mindig engedi, hogy a reteszek csak újból megpróbálják, és ugyanezt tegyék. Ez a megközelítés túlzott fejfájásokkal is járhat a gyakori szálkontextus-váltásban, ami csökkenti a rendszer általános teljesítményét. Ezenkívül a CPU ütemezője sem valósítja meg a méltányosságot, mivel nem tudja, melyik szál a leghosszabb ideig várakozott a zár (ok) ra.

Hagyjuk, hogy a szálak mindig szigorú sorrendben kérjenek zárolást

Például könnyebb mondani, mint megtenni. Ha olyan funkciót írunk, hogy pénzt utaljunk át az A számláról a B-re, írhatunk valami hasonlót

// fordításkor az első arg, majd a második zárolását vesszük figyelembe
nyilvános érvénytelen átutalás (A számla, B számla, hosszú pénz) {
  szinkronizált (A) {
    szinkronizált (B) {
      A.add (összeg);
      B.subtract (összeg);
    }
  }
}
// futási időben nem tudjuk követni, hogy a módszereket hogyan hívják meg
nyilvános érvénytelen futás () {
  új szál (() -> this.transfer (X, Y, 10000)) start ();
  új szál (() -> this.transfer (Y, X, 10000)) start ();
}
// ez a futtatás () holtpontot hoz létre
// az első szál X-re zár, vár Y-ra
// a második szál az Y-n záródik, várja az X-et

Valós megoldás

A valós szómegoldás eléréséhez kombinálhatjuk a zárolási rendezés és az időzített zárolás megközelítéseit

Üzleti által meghatározott zárolási rendelés

Javíthatjuk megközelítésünket azáltal, hogy megkülönböztetjük az A és B között az alapján, hogy kinek a számának nagyobb vagy kisebb.

// futáskor először kisebb azonosítóval vesszük figyelembe a zárolást
nyilvános érvénytelen átutalás (A számla, B számla, hosszú pénz) {
  végső számla először = A.id 
  szinkronizált (első) {
    szinkronizált (második) {
      first.add (összeg);
      second.subtract (összeg);
    }
  }
}
// futási időben nem tudjuk követni, hogy a módszereket hogyan hívják meg
nyilvános érvénytelen futás () {
  új szál (() -> this.transfer (X, Y, 10000)) start ();
  új szál (() -> this.transfer (Y, X, 10000)) start ();
}

Például, ha X.id = 1111 és Y.id = 2222, mivel az első számlát kisebb számla azonosítóval számoljuk, akkor az átutalás (Y, X, 10000) és az átutalás (X, Y, 10000) azonos lesz. Ha X számlaszáma Y-nál kevesebb, akkor mindkét szál megpróbálja lezárni X-et Y elõtt, és csak egyikük sikeres lesz, és folytatja az Y befejezés zárolását, majd X és Y zárolásainak engedését, mielõtt a többi szálat megszerezné a zárakat és folytatni tudja.

Üzleti által meghatározott időzített várakozás tryLock / async zárolási kérések

Az üzleti szempontból meghatározott zárrendelés alkalmazásának megoldása csak akkor működik asszociatív kapcsolatokban, ahol a logika egy helyen átadódik (….), Például a módszerünkben meghatározza az erőforrások összehangolásának módját.

Lehet, hogy rendelkezünk más módszerekkel / logikákkal is, amelyek rendelési logikát használnak, amely összeegyeztethetetlen az átvitelkel (…). A holtpontok elkerülése érdekében ilyen esetekben tanácsos az aszinkronizálás, ahol megpróbálunk egy erőforrást véges / realisztikus időre (max. Tranzakciós idő) + kicsi-véletlenszerű-várakozási időre zárolni, hogy az összes szál ne próbálja meg újra túl korán szerezzen be, és nem mindegyik egyszerre, ezzel elkerülve a Livelocks-ot (éhezés a zárak megszerzése iránti élettelen kísérletek miatt)

// Tegyük fel, hogy a # számlaszám: getLock () megadja a számla zárolását (java.util.concurrent.locks.Lock)
// A fiók befoglalhatja a zárolást, biztosíthatja a zárolást () / feloldást ()
public long getWait () {
/// visszatér az utolsó n átvitel átviteli idejének mozgó átlaga + kis-véletlenszerű só millis-ekben, így az összes zárolásra váró szál nem ébred fel egyszerre.
}
nyilvános érvénytelen átutalás (Lock lockF, Lock lockS, int összeg) {
  végső számla először = A.id 
  logikai kész = hamis;
  csináld {
    próbálja meg {
      próbálja meg {
        if (lockF.tryLock (getWait (), MILLISECONDS)) {
          próbálja meg {
            if (lockS.tryLock (getWait (), MILLISECONDS)) {
              kész = igaz;
            }
          } végül {
            lockS.unlock ();
          }
        }
      } fogás (InterruptedException e) {
        dobjon új RuntimeException ("törölt");
      }
    } végül {
      lockF.unlock ();
    }
  } while (! kész);

}
// futási időben nem tudjuk követni, hogy a módszereket hogyan hívják meg
nyilvános érvénytelen futás () {
    új szál (() -> this.transfer (X, Y, 10000)) start ();
    új szál (() -> this.transfer (Y, X, 10000)) start ();
}