Deadlocks & Livelocks- Bagaimana untuk dielakkan dalam Konvensyen dunia nyata?

Deadlocks boleh berlaku hanya dalam program Concurrent (multi-threaded) di mana benang menyegerakkan (menggunakan kunci) akses kepada satu atau lebih sumber yang dikongsi (pemboleh ubah dan objek) atau arahan-set (kritikal-bahagian).

Livelocks berlaku apabila kita cuba mengelakkan kebuntuan menggunakan kunci tak segerak, di mana beberapa benang bersaing untuk kunci set yang sama, elakkan memperoleh kunci untuk membolehkan benang lain pergi dengan kunci dahulu, dan akhirnya tidak dapat memperoleh kunci dan teruskan; menyebabkan kelaparan. Lihat di bawah untuk memahami bagaimana penguncian aysnc yang merupakan strategi untuk mengelakkan Deadlock boleh menjadi alasan untuk Livelock

Berikut adalah beberapa penyelesaian teori untuk Deadlocks, dan satu mereka (second one) adalah sebab utama untuk Livelocks

Pendekatan teorinya

Jangan gunakan kunci

Tidak mungkin di mana dua operasi perlu disegerakkan, misalnya, pemindahan bank mudah, di mana anda mendebitkan satu akaun sebelum anda boleh kredit akaun lain, dan tidak membiarkan benang lain menyentuh baki dalam akaun sehingga benang saat ini selesai.

Jangan blok pada kunci, jika benang tidak boleh memperoleh kunci, ia harus melepaskan kunci yang telah diperoleh sebelum ini untuk mencuba lagi kemudian

Tertelan untuk dilaksanakan dan boleh menyebabkan kelaparan (Livelocks) di mana benang sentiasa membiarkan kunci pergi hanya untuk mencuba lagi dan melakukan perkara yang sama. Juga, pendekatan ini mungkin mempunyai ketua-ketua dalam konteks thread yang kerap beralih mengurangkan prestasi keseluruhan sistem. Juga, tidak ada cara untuk penjadual CPU melaksanakan keadilan kerana ia tidak tahu benang mana sebenarnya telah menunggu kunci yang paling lama.

Biarkan benang sentiasa meminta kunci dalam pesanan yang ketat

Lebih mudah berkata daripada dilakukan, sebagai contoh. Jika kita sedang menulis fungsi untuk memindahkan wang dari Akaun A ke B, kita boleh menulis sesuatu seperti

/ / pada masa kompilasi, kita mengambil kunci pada arg pertama kemudian kedua
pemindahan tidak sah awam (Akaun A, Akaun B, wang lama) {
  disegerakkan (A) {
    disegerakkan (B) {
      A.add (amaun);
      B.subtract (amaun);
    }
  }
}
/ / semasa runtime kita tidak dapat menjejaki cara kita dipanggil
kekosongan awam lari () {
  Thread baru (() -> this.transfer (X, Y, 10000)). start ();
  Thread baru (() -> this.transfer (Y, X, 10000)). start ();
}
// lari ini () akan membuat kebuntuan
// kunci thread pertama pada X, menunggu Y
/ // kunci thread kedua pada Y, menunggu X

Penyelesaian dunia sebenar

Kami boleh menggabungkan pendekatan pesanan kunci dan kunci masa untuk sampai kepada penyelesaian perkataan sebenar

Perniagaan Menentukan Lock Ordering

Kami boleh memperbaiki pendekatan kami dengan mendiskriminasi A dan B berdasarkan nombor akaun yang lebih besar atau lebih kecil.

/ / pada waktu berjalan, kami mengambil kunci pada akaun dengan id yang lebih kecil terlebih dahulu
pemindahan tidak sah awam (Akaun A, Akaun B, wang lama) {
  akhir Akaun pertama = A.id 
  disegerakkan (dahulu) {
    disegerakkan (kedua) {
      first.add (jumlah);
      second.subtract (jumlah);
    }
  }
}
/ / semasa runtime kita tidak dapat menjejaki cara kita dipanggil
kekosongan awam lari () {
  Thread baru (() -> this.transfer (X, Y, 10000)). start ();
  Thread baru (() -> this.transfer (Y, X, 10000)). start ();
}

Contohnya, jika X.id = 1111, dan Y.id = 2222, kerana kita mengambil akaun pertama sebagai id yang lebih kecil, perintah penguncian untuk melaksanakan pemindahan (Y, X, 10000) dan transfer (X, Y, 10000) akan sama. X mempunyai nombor akaun yang lebih rendah daripada Y, kedua-dua benang akan cuba mengunci X sebelum Y dan hanya satu daripadanya akan berjaya dan terus mengunci Y menyelesaikan dan mengeluarkan kunci pada X dan Y sebelum benang lain memperoleh kunci dan boleh diteruskan.

Perniagaan Ditentukan Bermasalah Tunggu tryLock / async Mengunci Permintaan

Penyelesaian menggunakan kunci kerja yang ditentukan hanya berfungsi hanya untuk perhubungan bersekutu di mana logik di satu tempat pemindahan (....), Seperti dalam kaedah kami menentukan bagaimana sumber-sumber diselaraskan.

Kami mungkin mempunyai kaedah / logik lain, yang akhirnya menggunakan logik pesanan yang tidak sesuai dengan pemindahan (...). Untuk mengelakkan Deadlock dalam kes sedemikian, adalah disyorkan untuk menggunakan penguncian async, di mana kita cuba mengunci sumber untuk masa terhingga / realistik (masa transaksi max) + Rawak-tunggu masa kecil supaya semua benang tidak cuba dari re- memperoleh terlalu awal dan tidak semua pada masa yang sama masing-masing oleh itu mengelakkan Livelocks (kebuluran disebabkan oleh percubaan yang tidak dapat dibanggakan untuk memperoleh kunci)

// asumsikan Akaun # getLock () memberi kami kunci Akaun (java.util.concurrent.locks.Lock)
// Akaun boleh mengunci kunci, sediakan kunci () / buka kunci ()
getWait lama awam () {
/// mengembalikan purata pemindahan masa transfer untuk pemindahan n terakhir + garam kecil-rawak dalam millis jadi semua benang menunggu untuk mengunci tidak bangun pada masa yang sama.
}
pemindahan kekosongan awam (Kunci kunciF, kunci kunci, jumlah int) {
  akhir Akaun pertama = A.id 
  boolean done = false;
  do {
    cuba {
      cuba {
        jika (lockF.tryLock (getWait (), MILLISECONDS)) {
          cuba {
            jika (lockS.tryLock (getWait (), MILLISECONDS)) {
              selesai = benar;
            }
          } akhirnya {
            lockS.unlock ();
          }
        }
      } tangkapan (InterruptedException e) {
        buang RuntimeException baru ("Dibatalkan");
      }
    } akhirnya {
      lockF.unlock ();
    }
  } semasa (! selesai);

}
/ / semasa runtime kita tidak dapat menjejaki cara kita dipanggil
kekosongan awam lari () {
    Thread baru (() -> this.transfer (X, Y, 10000)). start ();
    Thread baru (() -> this.transfer (Y, X, 10000)). start ();
}