Apa perbedaan antara jalan buntu dan livelock?

Jawaban:

398

Diambil dari http://en.wikipedia.org/wiki/Deadlock :

Dalam komputasi bersamaan, kebuntuan adalah keadaan di mana setiap anggota grup tindakan, sedang menunggu beberapa anggota lainnya untuk melepaskan kunci

Sebuah livelock mirip dengan kebuntuan, kecuali bahwa negara-negara dari proses yang terlibat dalam livelock terus-menerus berubah sehubungan dengan satu sama lain, tidak ada kemajuan. Livelock adalah kasus khusus kelaparan sumber daya; definisi umum hanya menyatakan bahwa proses tertentu tidak mengalami kemajuan.

Contoh livelock di dunia nyata terjadi ketika dua orang bertemu di koridor sempit, dan masing-masing mencoba bersikap sopan dengan menyingkir untuk membiarkan yang lain lewat, tetapi mereka akhirnya bergoyang dari sisi ke sisi tanpa membuat kemajuan karena mereka berdua berulang kali bergerak dengan cara yang sama pada saat yang bersamaan.

Livelock adalah risiko dengan beberapa algoritma yang mendeteksi dan memulihkan dari kebuntuan. Jika lebih dari satu proses mengambil tindakan, algoritma deteksi kebuntuan dapat dipicu berulang kali. Ini dapat dihindari dengan memastikan bahwa hanya satu proses (dipilih secara acak atau berdasarkan prioritas) yang mengambil tindakan.

mah
sumber
8
Saya sudah menemukannya, tetapi mereka tidak memiliki contoh di sana seperti yang Anda lihat, terima kasih
macindows
61
Saya tidak akan memberikan contoh kode, tetapi pertimbangkan dua proses masing-masing menunggu sumber daya yang lain tetapi menunggu secara non-blocking. Ketika masing-masing mengetahui bahwa mereka tidak dapat melanjutkan, mereka melepaskan sumber daya yang tertahan dan tidur selama 30 detik, kemudian mereka mengambil sumber daya asli mereka diikuti dengan mencoba ke sumber daya proses lain yang diadakan, kemudian pergi, kemudian diminta kembali. Karena kedua proses berusaha untuk mengatasi (hanya buruk), ini adalah livelock.
mah
4
dapatkah Anda memberi saya contoh yang sama tetapi dengan jalan buntu, terima kasih sebelumnya
macindows
32
Contoh kebuntuan jauh lebih mudah ... mengasumsikan dua proses A dan B, dan masing-masing membutuhkan sumber daya r1 dan sumber daya r2. Asumsikan A menerima (atau sudah memiliki) r1, dan B menerima (atau sudah memiliki) r2. Sekarang masing-masing mencoba untuk mendapatkan sumber daya yang dimiliki orang lain, tanpa batas waktu. A diblokir karena B menampung r2, dan B diblokir karena A menahan r1. Setiap proses diblokir dan dengan demikian tidak dapat melepaskan sumber daya yang diinginkan, menyebabkan kebuntuan.
mah
2
Dalam konteks memori Transaksional, ada video hebat yang menunjukkan jalan buntu dan livelock: youtube.com/watch?v=_IxsOEEzf-c
BlackVegetable
78

Livelock

Sebuah utas sering bertindak sebagai respons terhadap tindakan utas lain. Jika tindakan utas lainnya juga merupakan respons terhadap tindakan utas lain, maka livelock dapat terjadi.

Seperti halnya jalan buntu, utas yang tidak dapat membuat kemajuan lebih lanjut . Namun, utas tidak diblokir - mereka terlalu sibuk merespons satu sama lain untuk melanjutkan pekerjaan . Ini sebanding dengan dua orang yang berusaha saling melewati di koridor: Alphonse bergerak ke kiri untuk membiarkan Gaston lewat, sementara Gaston bergerak ke kanannya untuk membiarkan Alphonse lewat. Melihat mereka masih saling menghalangi, Alphonse bergerak ke kanan, sementara Gaston bergerak ke kiri. Mereka masih saling menghalangi, dan seterusnya ...

Perbedaan utama antara livelock dan jalan buntu adalah bahwa utas tidak akan diblokir, sebaliknya mereka akan mencoba saling merespons secara terus menerus.

Pada gambar ini, kedua lingkaran (utas atau proses) akan mencoba memberi ruang kepada yang lain dengan bergerak ke kiri dan ke kanan. Tetapi mereka tidak bisa bergerak lebih jauh.

masukkan deskripsi gambar di sini

Buru
sumber
Contoh kode untuk livelocks stackoverflow.com/questions/1036364/good-example-of-livelock
Yauhen Yakimovich
1
Benda ini memiliki nama. Kata gaul mungkin, tapi tetap: schlumperdink : P
John Red
64

Semua konten dan contoh di sini berasal

Sistem Operasi: Internal dan Prinsip Desain
William Stallings
Edisi 8º

Jalan buntu : Situasi di mana dua atau lebih proses tidak dapat dilanjutkan karena masing-masing menunggu satu sama lain untuk melakukan sesuatu.

Sebagai contoh, pertimbangkan dua proses, P1 dan P2, dan dua sumber daya, R1 dan R2. Misalkan setiap proses memerlukan akses ke kedua sumber daya untuk melakukan bagian dari fungsinya. Maka dimungkinkan untuk memiliki situasi berikut: OS menetapkan R1 ke P2, dan R2 ke P1. Setiap proses menunggu salah satu dari dua sumber. Tidak satu pun akan melepaskan sumber daya yang sudah dimiliki sampai memiliki sumber daya lain dan melakukan fungsi yang membutuhkan kedua sumber daya. Kedua proses menemui jalan buntu

Livelock : Situasi di mana dua atau lebih proses secara terus-menerus mengubah keadaan mereka sebagai respons terhadap perubahan dalam proses lain tanpa melakukan pekerjaan yang bermanfaat:

Kelaparan : Suatu situasi di mana proses runnable diabaikan tanpa batas oleh penjadwal; meskipun ia dapat melanjutkan, itu tidak pernah dipilih.

Misalkan tiga proses (P1, P2, P3) masing-masing memerlukan akses berkala ke sumber daya R. Pertimbangkan situasi di mana P1 memiliki sumber daya, dan kedua P2 dan P3 tertunda, menunggu sumber daya itu. Ketika P1 keluar dari bagian kritisnya, P2 atau P3 harus diizinkan mengakses R. Asumsikan bahwa OS memberikan akses ke P3 dan bahwa P1 lagi membutuhkan akses sebelum P3 menyelesaikan bagian kritisnya. Jika OS memberikan akses ke P1 setelah P3 selesai, dan kemudian secara bergantian memberikan akses ke P1 dan P3, maka P2 tanpa batas waktu dapat ditolak akses ke sumber daya, meskipun tidak ada situasi jalan buntu.

LAMPIRAN A - TOPIK DALAM KONSURENSI

Contoh kebuntuan

Jika kedua proses mengatur flag mereka menjadi true sebelum salah satu telah menjalankan pernyataan while, maka masing-masing akan berpikir bahwa yang lain telah memasuki bagian kritisnya, menyebabkan kebuntuan.

/* PROCESS 0 */
flag[0] = true;            // <- get lock 0
while (flag[1])            // <- is lock 1 free?
    /* do nothing */;      // <- no? so I wait 1 second, for example
                           // and test again.
                           // on more sophisticated setups we can ask
                           // to be woken when lock 1 is freed
/* critical section*/;     // <- do what we need (this will never happen)
flag[0] = false;           // <- releasing our lock

 /* PROCESS 1 */
flag[1] = true;
while (flag[0])
    /* do nothing */;
/* critical section*/;
flag[1] = false;

Contoh Livelock

/* PROCESS 0 */
flag[0] = true;          // <- get lock 0
while (flag[1]){         
    flag[0] = false;     // <- instead of sleeping, we do useless work
                         //    needed by the lock mechanism
    /*delay */;          // <- wait for a second
    flag[0] = true;      // <- and restart useless work again.
}
/*critical section*/;    // <- do what we need (this will never happen)
flag[0] = false; 

/* PROCESS 1 */
flag[1] = true;
while (flag[0]) {
    flag[1] = false;
    /*delay */;
    flag[1] = true;
}
/* critical section*/;
flag[1] = false;

[...] pertimbangkan urutan kejadian berikut:

  • P0 menetapkan flag [0] menjadi true.
  • P1 menetapkan flag [1] menjadi true.
  • P0 memeriksa flag [1].
  • P1 memeriksa flag [0].
  • P0 menetapkan flag [0] menjadi false.
  • P1 menetapkan flag [1] menjadi false.
  • P0 menetapkan flag [0] menjadi true.
  • P1 menetapkan flag [1] menjadi true.

Urutan ini dapat diperpanjang tanpa batas waktu, dan tidak ada proses yang bisa masuk ke bagian kritisnya. Sebenarnya, ini bukan jalan buntu , karena setiap perubahan dalam kecepatan relatif dari dua proses akan memutus siklus ini dan memungkinkan seseorang untuk memasuki bagian kritis. Kondisi ini disebut livelock . Ingat bahwa kebuntuan terjadi ketika serangkaian proses ingin memasuki bagian kritis mereka tetapi tidak ada proses yang bisa berhasil. Dengan livelock , ada kemungkinan urutan eksekusi yang berhasil, tetapi juga dimungkinkan untuk menggambarkan satu atau lebih urutan eksekusi di mana tidak ada proses yang pernah memasuki bagian kritisnya.

Tidak puas dari buku lagi.

Dan bagaimana dengan spinlocks?

Spinlock adalah teknik untuk menghindari biaya mekanisme kunci OS. Biasanya yang akan Anda lakukan:

try
{
   lock = beginLock();
   doSomething();
}
finally
{
   endLock();
}

Masalah mulai muncul ketika beginLock()harganya jauh lebih mahal doSomething(). Dalam istilah yang sangat luas, bayangkan apa yang terjadi ketika beginLockbiaya 1 detik, tetapi doSomethingbiaya hanya 1 milidetik.

Dalam hal ini jika Anda menunggu 1 milidetik, Anda akan terhindar selama 1 detik.

Mengapa beginLockharganya sangat mahal? Jika kunci gratis tidak memerlukan biaya banyak (lihat https://stackoverflow.com/a/49712993/5397116 ), tetapi jika kunci tidak gratis OS akan "membekukan" utas Anda, siapkan mekanisme untuk membangunkan Anda ketika kunci dibebaskan, dan kemudian membangunkan Anda lagi di masa depan.

Semua ini jauh lebih mahal daripada beberapa loop yang memeriksa kunci. Itulah sebabnya terkadang lebih baik melakukan "spinlock".

Sebagai contoh:

void beginSpinLock(lock)
{
   if(lock) loopFor(1 milliseconds);
   else 
   {
     lock = true;
     return;
   }

   if(lock) loopFor(2 milliseconds);
   else 
   {
     lock = true;
     return;
   }

   // important is that the part above never 
   // cause the thread to sleep.
   // It is "burning" the time slice of this thread.
   // Hopefully for good.

   // some implementations fallback to OS lock mechanism
   // after a few tries
   if(lock) return beginLock(lock);
   else 
   {
     lock = true;
     return;
   }
}

Jika implementasi Anda tidak hati-hati, Anda dapat jatuh cinta, menghabiskan semua CPU pada mekanisme kunci.

Lihat juga:

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
Apakah implementasi penguncian spin saya benar dan optimal?

Ringkasan :

Deadlock : situasi di mana tidak ada yang maju, tidak melakukan apa-apa (tidur, menunggu dll.). Penggunaan CPU akan rendah;

Livelock : situasi di mana tidak ada kemajuan, tetapi CPU dihabiskan untuk mekanisme kunci dan bukan pada perhitungan Anda;

Kelaparan: situasi di mana satu penuntut tidak pernah mendapat kesempatan untuk lari; karena nasib buruk atau oleh beberapa propertinya (prioritas rendah, misalnya);

Spinlock : teknik menghindari biaya menunggu kunci dibebaskan.

Daniel Frederico Lins Leite
sumber
Pak, contoh yang Anda berikan untuk Deadlock sebenarnya adalah contoh Spinlock. Kebuntuan terjadi ketika serangkaian proses diblokir yang tidak dalam keadaan siap atau berjalan dan menunggu beberapa sumber daya. Tetapi dalam contoh kita masing-masing melakukan beberapa tugas yaitu, memeriksa kondisi lagi dan lagi. Koreksi saya jika saya salah.
Vinay Yadav
Contohnya sangat minim sehingga membuka peluang untuk penafsiran ini, jadi saya memperbaikinya untuk menjadi sedikit lebih eksplisit tentang perbedaan mereka. Semoga itu bisa membantu.
Daniel Frederico Lins Leite
Terima kasih telah menambahkan tentang spinlocks, menurut Anda spinlocks adalah teknik dan Anda membenarkannya juga dan saya mengerti. Tapi bagaimana dengan masalah inversi prioritas ketika satu proses P1 berada di Bagian Kritis dan proses prioritas tinggi lainnya P2 dijadwalkan mendahului P1 maka dalam hal ini CPU dengan P2 dan mekanisme Sinkronisasi kami adalah dengan P1. Ini disebut Spinlock karena P1 ada di siap negara dan P2 dalam jangka negara. Di sini spinlock adalah masalah. Apakah saya memperbaikinya? Saya tidak bisa mendapatkan seluk-beluknya dengan benar. Tolong bantu
Vinay Yadav
Saran saya kepada Anda adalah membuat pertanyaan lain yang menyatakan masalah Anda lebih jelas. Sekarang, jika Anda berada di "ruang pengguna", dan P1 berada di dalam sesi kritis yang dilindungi dengan SpinLock yang diimplementasikan dengan loop tak terbatas dan preemptinya; maka P2 akan mencoba memasukkannya, akan gagal dan akan membakar semua waktunya-slice. Anda membuat livelock (satu CPU akan mencapai 100%). (Penggunaan yang buruk akan melindungi IO sinkronisasi dengan spinlock. Anda dapat dengan mudah mencoba contoh ini) Pada "ruang kernel" mungkin catatan ini dapat membantu Anda: lxr.linux.no/linux+v3.6.6/Documentation/…
Daniel Frederico Lins Leite
Terima kasih banyak atas klarifikasi ini. Bagaimanapun, jawaban Anda cukup deskriptif dan membantu tidak seperti orang lain
Vinay Yadav
13

DEADLOCK Deadlock adalah suatu kondisi di mana tugas menunggu tanpa batas untuk kondisi yang tidak pernah dapat dipenuhi - tugas mengklaim kontrol eksklusif atas sumber daya bersama - tugas memegang sumber daya sambil menunggu sumber daya lainnya akan dirilis - tugas tidak dapat dipaksa untuk melemahkan sumber daya - menunggu melingkar kondisi ada

LIVELOCK Kondisi Livelock dapat muncul ketika dua atau lebih tugas bergantung pada dan menggunakan beberapa sumber daya yang menyebabkan kondisi dependensi melingkar di mana tugas-tugas terus berjalan selamanya, sehingga memblokir semua tugas tingkat prioritas yang lebih rendah dari berjalan (tugas-tugas prioritas yang lebih rendah ini mengalami kondisi yang disebut kelaparan)

Deepak Lamichhane
sumber
Jika tugas 'livelocked' mengikuti protokol arbitrase sumber daya yang mencakup keterlambatan 'backoff', dan menghabiskan sebagian besar waktu mereka dalam kondisi tidur, maka tugas lain tidak akan kelaparan.
greggo
8

Mungkin dua contoh ini menggambarkan Anda perbedaan antara jalan buntu dan livelock:


Java-Contoh untuk kebuntuan:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockSample {

    private static final Lock lock1 = new ReentrantLock(true);
    private static final Lock lock2 = new ReentrantLock(true);

    public static void main(String[] args) {
        Thread threadA = new Thread(DeadlockSample::doA,"Thread A");
        Thread threadB = new Thread(DeadlockSample::doB,"Thread B");
        threadA.start();
        threadB.start();
    }

    public static void doA() {
        System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
        lock1.lock();
        System.out.println(Thread.currentThread().getName() + " : holds lock 1");

        try {
            System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
            lock2.lock();
            System.out.println(Thread.currentThread().getName() + " : holds lock 2");

            try {
                System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
            } finally {
                lock2.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
            }
        } finally {
            lock1.unlock();
            System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
        }
    }

    public static void doB() {
        System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
        lock2.lock();
        System.out.println(Thread.currentThread().getName() + " : holds lock 2");

        try {
            System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
            lock1.lock();
            System.out.println(Thread.currentThread().getName() + " : holds lock 1");

            try {
                System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
            } finally {
                lock1.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
            }
        } finally {
            lock2.unlock();
            System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
        }
    }
}

Output sampel:

Thread A : waits for lock 1
Thread B : waits for lock 2
Thread A : holds lock 1
Thread B : holds lock 2
Thread B : waits for lock 1
Thread A : waits for lock 2

Java-Contoh untuk livelock:


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LivelockSample {

    private static final Lock lock1 = new ReentrantLock(true);
    private static final Lock lock2 = new ReentrantLock(true);

    public static void main(String[] args) {
        Thread threadA = new Thread(LivelockSample::doA, "Thread A");
        Thread threadB = new Thread(LivelockSample::doB, "Thread B");
        threadA.start();
        threadB.start();
    }

    public static void doA() {
        try {
            while (!lock1.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
                Thread.sleep(100);
            }
            System.out.println(Thread.currentThread().getName() + " : holds lock 1");

            try {
                while (!lock2.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
                    Thread.sleep(100);
                }
                System.out.println(Thread.currentThread().getName() + " : holds lock 2");

                try {
                    System.out.println(Thread.currentThread().getName() + " : critical section of doA()");
                } finally {
                    lock2.unlock();
                    System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
                }
            } finally {
                lock1.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
            }
        } catch (InterruptedException e) {
            // can be ignored here for this sample
        }
    }

    public static void doB() {
        try {
            while (!lock2.tryLock()) {
                System.out.println(Thread.currentThread().getName() + " : waits for lock 2");
                Thread.sleep(100);
            }
            System.out.println(Thread.currentThread().getName() + " : holds lock 2");

            try {
                while (!lock1.tryLock()) {
                    System.out.println(Thread.currentThread().getName() + " : waits for lock 1");
                    Thread.sleep(100);
                }
                System.out.println(Thread.currentThread().getName() + " : holds lock 1");

                try {
                    System.out.println(Thread.currentThread().getName() + " : critical section of doB()");
                } finally {
                    lock1.unlock();
                    System.out.println(Thread.currentThread().getName() + " : does not hold lock 1 any longer");
                }
            } finally {
                lock2.unlock();
                System.out.println(Thread.currentThread().getName() + " : does not hold lock 2 any longer");
            }
        } catch (InterruptedException e) {
            // can be ignored here for this sample
        }
    }
}

Output sampel:

Thread B : holds lock 2
Thread A : holds lock 1
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
Thread B : waits for lock 1
Thread A : waits for lock 2
Thread A : waits for lock 2
Thread B : waits for lock 1
...

Kedua contoh memaksa utas untuk mendapatkan kunci dalam pesanan yang berbeda. Sementara kebuntuan menunggu kunci lainnya, livelock tidak benar-benar menunggu - itu mati-matian mencoba untuk mendapatkan kunci tanpa kesempatan mendapatkannya. Setiap percobaan mengkonsumsi siklus CPU.

mmirwaldt
sumber
Kodenya bagus. Tetapi contoh live-lock tidak baik. Apakah utas diblokir pada nilai atau polling untuk perubahan nilai tidak secara konseptual berbeda. Perubahan mudah untuk mengilustrasikan kunci-hidup dengan lebih baik adalah dengan membuat utas A dan B melepaskan kunci yang mereka miliki ketika mereka menyadari bahwa mereka tidak bisa mendapatkan kunci kedua yang mereka butuhkan. Kemudian mereka masing-masing tidur selama satu detik, mendapatkan kembali kunci yang semula mereka miliki, kemudian tidur selama satu detik dan mencoba untuk mendapatkan kunci yang lain lagi. Jadi siklus untuk masing-masing akan menjadi: 1) mendapatkan-tambang, 2) tidur, 3) mencoba untuk mendapatkan yang lain & gagal, 4) melepaskan-tambang, 5) tidur, 6) Ulangi.
CognizantApe
1
Saya ragu apakah kunci hidup yang Anda pikir benar-benar ada cukup lama sehingga menyebabkan masalah. Ketika Anda selalu melepaskan semua kunci yang Anda pegang ketika Anda tidak dapat mengalokasikan kunci berikutnya, kondisi deadlock (dan live-lock) "tahan dan tunggu" hilang karena sebenarnya tidak ada menunggu lagi. ( en.wikipedia.org/wiki/Deadlock )
mmirwaldt
memang kondisi kunci mati hilang karena ini adalah kunci hidup yang sedang kita diskusikan. Contoh yang saya berikan mirip dengan contoh lorong standar yang diberikan: geeksforgeeks.org/deadlock-starvation-and-livelock , en.wikibooks.org/wiki/Operating_System_Design/Concurrency/… , docs.oracle.com/javase/tutorial/essential / concurrency / ...
CognizantApe
0

Bayangkan Anda memiliki utas A dan utas B. Keduanya berada synchroniseddi objek yang sama dan di dalam blok ini ada variabel global yang keduanya diperbarui;

static boolean commonVar = false;
Object lock = new Object;

...

void threadAMethod(){
    ...
    while(commonVar == false){
         synchornized(lock){
              ...
              commonVar = true
         }
    }
}

void threadBMethod(){
    ...
    while(commonVar == true){
         synchornized(lock){
              ...
              commonVar = false
         }
    }
}

Jadi, ketika benang A masuk dalam whilelingkaran dan memegang kunci, itu tidak apa yang harus dilakukan dan mengatur commonVaruntuk true. Kemudian benang B masuk, masuk dalam whilelingkaran dan karena commonVarini truesekarang, itu dapat memegang kunci. Itu melakukannya, mengeksekusi synchronisedblok, dan mengatur commonVarkembali ke false. Sekarang, benang A lagi mendapat itu jendela CPU baru, itu adalah tentang untuk keluar dari whilelingkaran tapi thread B baru saja mengatur kembali ke false, sehingga siklus berulang lagi. Utas melakukan sesuatu (jadi mereka tidak diblokir dalam arti tradisional) tetapi untuk hampir tidak ada.

Mungkin juga menyenangkan untuk menyebutkan bahwa livelock tidak harus muncul di sini. Saya berasumsi bahwa scheduler lebih menyukai utas lainnya setelah synchronisedblok selesai dieksekusi. Sebagian besar waktu, saya pikir itu adalah ekspektasi yang sulit dicapai dan tergantung pada banyak hal yang terjadi di bawah tenda.

stdout
sumber
Contoh yang bagus. Ini juga menggambarkan mengapa Anda harus selalu membaca dan menulis secara atomis dalam konteks bersamaan. Jika loop sementara berada di dalam blok sinkronisasi maka di atas tidak akan menjadi masalah.
CognizantApe