Deadlock ve livelock arasındaki fark nedir?


Yanıtlar:


398

Alındığı http://en.wikipedia.org/wiki/Deadlock :

Eşzamanlı hesaplamada, kilitlenme , bir grup eylemin her üyesinin, başka bir üyenin bir kilit açmasını beklediği bir durumdur

Bir livelock livelock katılan süreçlerin devletler sürekli birbirlerine ilişkin, hiçbiri ilerleyen ile değiştirmek dışında çıkmaza benzer. Livelock özel bir kaynak açlığı vakasıdır; genel tanım sadece belirli bir sürecin ilerlemediğini belirtir.

Gerçek dünyadaki canlı kilit örneği, iki kişi dar bir koridorda buluştuğunda ortaya çıkar ve her biri, diğerinin geçmesine izin vermek için bir kenara hareket ederek kibar olmaya çalışır, ancak her ikisi de tekrar tekrar hareket etmeden herhangi bir ilerleme olmadan yan yana sallanırlar. aynı şekilde.

Livelock, kilitlenmeyi algılayan ve kurtarmayı engelleyen bazı algoritmalar için bir risktir. Birden fazla işlem harekete geçerse, kilitlenme algılama algoritması tekrar tekrar tetiklenebilir. Bu, yalnızca bir işlemin (rastgele veya öncelikli olarak seçilen) harekete geçmesini sağlayarak önlenebilir.


8
Zaten buldum, ama orada gördüğünüz gibi örnekleri yok, yine de teşekkürler
macindows

61
Bir kod örneği vermeyeceğim, ancak her biri diğerinin sahip olduğu ancak engellemeyen bir şekilde bekleyen bir kaynağı bekleyen iki işlemi düşünün. Her biri devam edemediklerini öğrendiklerinde, tuttukları kaynağı serbest bırakırlar ve 30 saniye uyurlar, daha sonra orijinal kaynaklarını alırlar, ardından diğer süreçte tutulan kaynağı dener, sonra bırakılır, sonra yeniden gerekirler. Her iki süreç de (kötü bir şekilde) baş etmeye çalıştığından, bu bir livelock.
mah

4
bana aynı örneği verebilir misin ama kilitlenme ile, şimdiden teşekkürler
macindows

32
Bir kilitlenme örneği çok daha kolaydır ... iki A ve B işlemi olduğunu varsayalım ve her biri r1 kaynağı ve r2 kaynağı istiyor. A'nın r1 aldığını (veya zaten olduğunu) ve B'nin r2 aldığını (veya zaten olduğunu) varsayalım. Şimdi her biri, zaman aşımı olmadan diğerinin sahip olduğu kaynağı almaya çalışır. A, r2'yi tuttuğu için A engellenir ve A, r1'i tuttuğu için B engellenir. Her işlem engellenir ve böylece diğerinin istediği kaynağı serbest bırakarak kilitlenmeye neden olmaz.
mah

2
İşlemsel bellek bağlamında kilitlenmeyi ve canlı kilidi gösteren harika bir video var: youtube.com/watch?v=_IxsOEEzf-c
BlackVegetable

78

Livelock

Bir iş parçacığı genellikle başka bir iş parçacığının eylemine yanıt olarak çalışır. Diğer iş parçacığının eylemi de başka bir iş parçacığının eylemine bir yanıtsa, livelock oluşabilir.

Kilitlenmede olduğu gibi, kilitlenmemiş iş parçacıkları daha fazla ilerleme kaydedemez . Ancak, iş parçacıkları engellenmez - işi sürdürmek için birbirlerine yanıt vermekle meşguller . Bu, bir koridorda birbirini geçmeye çalışan iki kişi ile karşılaştırılabilir: Alphonse, Gaston'un geçmesine izin vermek için sola hareket ederken Gaston, Alphonse'un geçmesine izin vermek için sağa hareket eder. Hala birbirlerini bloke ettiklerini gören Alphonse sağa, Gaston sola hareket ediyor. Hala birbirlerini engelliyorlar, vb.

Canlı kilit ve kilitlenme arasındaki temel fark, evrelerin engellenmeyecekleri, bunun yerine sürekli olarak yanıt vermeye çalışacaklarıdır.

Bu görüntüde, her iki daire (iş parçacıkları veya işlemler) sola ve sağa hareket ederek diğerine yer vermeye çalışacaktır. Ama daha fazla ilerleyemezler.

resim açıklamasını buraya girin



1
Bu şeyin bir adı var. Bir argo kelime belki, ama yine de: schlumperdink : P
John Red

64

Buradaki tüm içerik ve örnekler

İşletim Sistemleri: İç ve Tasarım İlkeleri
William Stallings
8º Edition

Kilitlenme : İki veya daha fazla işlemin devam edemediği bir durumdur, çünkü her biri diğerinin bir şey yapmasını beklemektedir.

Örneğin, iki işlemi düşünün, P1 ve P2 ve iki kaynağı, R1 ve R2. Her işlemin, işlevinin bir bölümünü gerçekleştirmek için her iki kaynağa da erişmesi gerektiğini varsayalım. Daha sonra aşağıdaki duruma sahip olmak mümkündür: OS, R1'i P2'ye ve R2'yi P1'i atar. Her süreç iki kaynaktan birini bekliyor. İkisi de, diğer kaynağı edinene ve her iki kaynağı gerektiren işlevi yerine getirene kadar zaten sahip olduğu kaynağı serbest bırakmaz. İki süreç kilitlendi

Livelock : İki veya daha fazla sürecin yararlı bir iş yapmadan diğer süreç (ler) deki değişikliklere karşılık olarak durumlarını sürekli olarak değiştirdiği bir durum:

Açlık : Çalıştırılabilir bir sürecin zamanlayıcı tarafından süresiz olarak gözden kaçırıldığı bir durum; ilerleyebilmesine rağmen, asla seçilmez.

Üç işlemin (P1, P2, P3) her birinin R kaynağına periyodik erişim gerektirdiğini varsayalım. P1'in kaynağa sahip olduğu durumu ve hem P2 hem de P3'ün geciktiğini ve bu kaynağı beklediğini düşünün. P1 kritik bölümünden çıktığında, P2 veya P3'ün R'ye erişmesine izin verilmelidir. İşletim sisteminin P3'e erişim izni verdiğini ve P3'ün kritik bölümünü tamamlamadan önce P1'in tekrar erişim gerektirdiğini varsayın. İşletim sistemi, P3 bittikten sonra P1'e erişim izni verirse ve daha sonra alternatif olarak P1 ve P3'e erişim izni verirse, herhangi bir kilitlenme durumu olmasa bile P2'nin kaynağa erişimi sınırsız olarak reddedilebilir.

EK A - BİRLEŞİKTE KONULAR

Kilitlenme Örneği

Her iki işlem de while deyimini yürütmeden önce bayraklarını true değerine ayarlarsa, her biri diğerinin kritik bölümüne girdiğini ve kilitlenmeye neden olduğunu düşünür.

/* 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;

Livelock Örneği

/* 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;

[...] aşağıdaki olay sırasını göz önünde bulundurun:

  • P0, [0] işaretini true olarak ayarlar.
  • P1 [1] işaretini true olarak ayarlar.
  • P0 bayrağı [1] kontrol eder.
  • P1 [0] bayrağını kontrol eder.
  • P0, [0] işaretini false olarak ayarlar.
  • P1, [1] işaretini false olarak ayarlar.
  • P0, [0] işaretini true olarak ayarlar.
  • P1 [1] işaretini true olarak ayarlar.

Bu dizi süresiz olarak uzatılabilir ve her iki süreç de kritik bölümüne giremez. Açıkçası, bu bir kilitlenme değildir , çünkü iki sürecin nispi hızında herhangi bir değişiklik bu döngüyü kıracak ve kritik bölüme girmeye izin verecektir. Bu duruma livelock adı verilir . Kilitlenmenin bir dizi işlem kritik bölümlerine girmek istediğinde gerçekleştiğini ancak hiçbir işlemin başarılı olamayacağını hatırlayın. Livelock ile başarılı olan olası yürütme dizileri vardır, ancak hiçbir sürecin kritik bölümüne girmediği bir veya daha fazla yürütme dizisini tanımlamak da mümkündür.

Artık kitaptan içerik değil.

Peki ya spinlocks?

Spinlock, OS kilit mekanizmasının maliyetinden kaçınmak için bir tekniktir. Genellikle şunları yaparsınız:

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

beginLock()Maliyet çok daha fazla olduğunda bir sorun ortaya çıkmaya başlar doSomething(). Çok abartılı terimlerle, beginLockmaliyetlerin 1 saniye, ancak doSomethingsadece 1 milisaniyede ne olacağını hayal edin .

Bu durumda 1 milisaniye beklerseniz, 1 saniye boyunca engellenmekten kaçınabilirsiniz.

Neden beginLockbu kadar pahalıya mal olacak? Kilit ücretsizse çok pahalı değilse (bkz. Https://stackoverflow.com/a/49712993/5397116 ), ancak kilit ücretsiz değilse işletim sistemi iş parçacığınızı "dondurur", sizi uyandırmak için bir mekanizma kurun kilit serbest olduğunda, ve sonra gelecekte tekrar uyanmak.

Tüm bunlar kilidi kontrol eden bazı döngülerden çok daha pahalıdır. Bu yüzden bazen bir "spinlock" yapmak daha iyidir.

Örneğin:

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;
   }
}

Uygulamanız dikkatli değilse, tüm CPU'yu kilit mekanizmasına harcayarak livelock'a düşebilirsiniz.

Ayrıca bakınız:

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
Döndürme kilidi uygulamam doğru ve optimum mu?

Özet :

Kilitlenme : kimsenin ilerlemediği, hiçbir şey yapmaması (uyku, bekleme vb.) Durum. CPU kullanımı düşük olacaktır;

Livelock : kimsenin ilerlemediği durum, ancak CPU hesaplamaya değil kilit mekanizmasına harcanır;

Açlık: Bir işleyicinin asla kaçma şansı bulamadığı durum; saf kötü şans ya da mülkünün bir kısmı tarafından (örneğin düşük öncelikli);

Spinlock : Kilidin serbest bırakılmasını bekleyen maliyetlerden kaçınma tekniği.


Efendim, Deadlock için verdiğiniz örnek aslında bir Spinlock örneğidir. Kilitlenme, hazır veya çalışır durumda olmayan ve bazı kaynakları bekleyen bir dizi işlem engellendiğinde oluşur. Ancak örneğimizde her biri bir işi yerine getiriyor, yani durumu tekrar tekrar kontrol ediyor. Eğer Yanlışsam beni düzelt.
Vinay Yadav

Örnek o kadar azdır ki, bu yorum için açık şans sağlar, bu yüzden farkları hakkında biraz daha açık olmasını sağladım. Umarım yardımcı olur.
Daniel Frederico Lins Leite

Spinlocks hakkında eklediğiniz için teşekkür ederiz, size göre spinlocks bir teknik ve u da haklı ve anladım. P1, bir işlem P1 Kritik Bölümdeyken ve diğer yüksek öncelikli işlem P2, P1'i önleyerek zamanlanırsa, bu durumda CPU P2 ile ve Senkronizasyon mekanizmamız P1 ile olduğunda zamanlanır. P1 hazır durumda ve P2 çalışma durumunda olduğu için buna Spinlock denir . Burada spinlock bir sorundur. İşleri doğru mu yapıyorum? Karmaşıklıkları doğru anlayamıyorum. Lütfen yardım
Vinay Yadav

Size önerim, sorununuzu daha açık bir şekilde belirten başka bir soru oluşturmaktır. Şimdi, "kullanıcı alanı" içindeyseniz ve P1, sonsuz bir döngü ile uygulanan bir SpinLock ile korunan kritik bir oturum içindeyse ve bunun önlenmesi; P2 girmeye çalışır, başarısız olur ve tüm zaman dilimlerini yakar. Bir livelock oluşturdunuz (bir CPU% 100'de olacak). (kötü bir kullanım, spinlock ile senkronize bir G / Ç'yi
Daniel Frederico Lins Leite

Açıklama için çok teşekkür ederim. Her neyse, cevabınız diğerlerinden farklı olarak oldukça açıklayıcı ve yardımcı oldu
Vinay Yadav

13

DEADLOCK Deadlock, bir görevin asla tatmin edilemeyen koşulları süresiz olarak beklediği bir durumdur - görev paylaşılan kaynaklar üzerinde özel kontrol talep eder - görev diğer kaynakların serbest bırakılmasını beklerken kaynakları tutar - görevler kaynakları ayırt etmek zorunda bırakılamaz - dairesel bir bekleme durum var

LIVELOCK Livelock koşulları, iki veya daha fazla görev bağımlı olduğunda ve bu kaynakların sonsuza kadar çalışmaya devam ettiği dairesel bağımlılık koşuluna neden olan bazı kaynakları kullandığında ortaya çıkabilir, böylece tüm düşük öncelikli görevlerin çalışmasını engeller (bu düşük öncelikli görevler açlık denen bir durum yaşar)


'Canlı kilitli' görevler, 'geri çekilme' gecikmelerini içeren kaynak tahkim protokollerini takip ediyorsa ve zamanlarının çoğunu uyku durumunda geçirirse, diğer görevler aç bırakılmaz.
greggo

8

Belki bu iki örnek size bir kilitlenme ve bir kilitlenme arasındaki farkı göstermektedir:


Bir kilitlenme için Java Örneği:

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");
        }
    }
}

Örnek çıktı:

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

Canlı kilit için Java örneği:


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
        }
    }
}

Örnek çıktı:

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
...

Her iki örnek de iplikleri kilitleri farklı sıralarda almaya zorlar. Kilitlenme diğer kilidi beklerken, livelock gerçekten beklemez - umutsuzca kilidi alma şansı olmadan kilidi almaya çalışır. Her deneme CPU döngülerini tüketir.


Kod güzel. Ancak canlı kilit örneği iyi değil. Bir iş parçacığının bir değer üzerinde engellenip engellenmediği veya değerdeki bir değişiklik için yoklama olup olmadığı kavramsal olarak farklı değildir. Canlı kilidi daha iyi göstermek için kolay bir değişiklik, ihtiyaç duydukları ikinci kilidi alamadıklarını fark ettiklerinde A ve B dişlerinin sahip oldukları kilitleri serbest bırakmalarıdır. Sonra her biri bir saniye uyuyorlar, başlangıçta sahip oldukları kilidi yeniden alıyorlar, sonra bir saniye daha uyuyorlar ve diğer kilidi tekrar almaya çalışıyorlar. Yani her biri için döngü şöyle olurdu: 1) edinme mayını, 2) uyku, 3) başka edinmeyi ve başarısız olmayı deneyin, 4) serbest bırakma mayını, 5) uyku, 6) Tekrarlayın.
CognizantApe

1
Düşündüğünüz canlı kilitlerin sorun yaratacak kadar uzun olup olmadığından şüpheliyim. Bir sonraki kilidi ayıramadığınızda tuttuğunuz tüm kilitlerden her zaman vazgeçtiğinizde, kilitlenme (ve canlı kilit) koşulu "bekle ve bekle" durumu eksiktir çünkü artık beklemek yoktur. ( en.wikipedia.org/wiki/Deadlock )
mmirwaldt

Gerçekten ölü kilit durumu eksik çünkü bunlar tartıştığımız canlı kilitler. Verdiğim örnek verilen standart koridor örneğine benziyor : geeksforgeeks.org/deadlock-starvation-and-livelock , en.wikibooks.org/wiki/Operating_System_Design/Concurrency/… , docs.oracle.com/javase/tutorial/essential / concurrency /…
CognizantApe

0

A iş parçacığı ve B iş parçacığı olduğunu düşünün. Her ikisi synchronisedde aynı nesnede ve bu bloğun içinde her ikisinin de güncellediği genel bir değişken var;

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
         }
    }
}

İplik A girdiğinde Yani, whiledöngü ve kilit tutan, o şekilde davranmak ve set neler yapar commonVariçin true. Sonra B, gelir de girer iplik whiledöngü ve yana commonVarolduğunu trueşimdi, kiliti muktedir olduğunu. Bunu yapar, synchronisedbloğu yürütür ve commonVargeri ayarlanır false. Şimdi, iplik A yine 's yeni işlemci pencere alır, bu oldu bırakmak üzereyken whiledöngü ama iplik B sadece geri onu belirledi falsedöngü tekrarlanır böylece tekrar tekrar,. İplikler bir şey yaparlar (geleneksel anlamda engellenmezler) ama hemen hemen hiçbir şey için.

Livelock'un burada görünmesi gerekmediğini belirtmek de güzel olabilir. synchronisedBlok bittikten sonra zamanlayıcı diğer iş parçacığı tercih varsayıyorum . Çoğu zaman, bunun zor bir beklenti olduğunu ve kaputun altında gerçekleşen birçok şeye bağlı olduğunu düşünüyorum.


Güzel bir örnek. Aynı zamanda neden her zaman eşzamanlı bir bağlamda atomik olarak okumalı ve yazmalısınız. While döngüleri senkronizasyon bloklarının içinde olsaydı, yukarıdakiler sorun olmazdı.
CognizantApe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.