교착 상태와 라이브 록의 차이점은 무엇입니까?


답변:


398

http://en.wikipedia.org/wiki/Deadlock 에서 가져온 것 :

동시 컴퓨팅에서 교착 상태 는 조치 그룹의 각 구성원이 다른 구성원이 잠금을 해제하기를 기다리는 상태입니다.

라이브 락은 라이브 록에 관련된 프로세스의 상태가 지속적으로 서로 관련, 아무것도 진행하지으로 변경하는 것을 제외하고, 교착 상태와 유사하다. Livelock은 특별한 리소스 기아 사례입니다. 일반 정의는 특정 프로세스가 진행 중이 아님을 나타냅니다.

라이브 록의 실제 예는 두 사람이 좁은 복도에서 만났을 때 발생하며, 각 사람은 다른 사람이 지나갈 수 있도록 옆으로 움직여 공 손해 지려고하지만 둘 다 반복적으로 움직이기 때문에 아무런 진전없이 좌우로 흔들립니다. 동시에 같은 방식으로.

라이브 록은 교착 상태를 감지하고 복구하는 일부 알고리즘의 위험입니다. 둘 이상의 프로세스가 조치를 취하면 교착 상태 감지 알고리즘이 반복적으로 트리거 될 수 있습니다. 하나의 프로세스 (임의로 또는 우선 순위로 선택) 만 조치를 수행하여이를 방지 할 수 있습니다.


8
나는 이미 그것을 발견하지만, 어쨌든 당신이 볼 수 있기 때문에 예, 감사가없는
macindows

61
코드 예제를 제공하지는 않지만 각각 다른 프로세스가 리소스를 대기하고 비 차단 방식으로 대기하는 두 프로세스를 고려하십시오. 각 학습자가 학습을 계속할 수 없으면 보유한 자원을 해제하고 30 초 동안 휴면 한 다음 원래 자원을 검색 한 다음 다른 프로세스가 보유한 자원을 시도한 후 떠난 다음 재 시도합니다. 두 프로세스가 모두 잘못 대처하려고하기 때문에 이것은 라이브 록입니다.
mah may

4
당신은 나에게 같은 예를 제공하지만 교착 상태로 미리 감사드립니다
macindows

32
교착 상태 예제는 훨씬 쉽습니다. 두 프로세스 A와 B를 가정하고 각각 리소스 r1과 리소스 r2를 원한다고 가정합니다. A가 r1을 받거나 이미 가지고 있고 B가 r2를 받거나 가지고 있다고 가정합니다. 이제 각각은 시간 초과없이 상대방이 가진 리소스를 얻으려고합니다. B는 r2를 보유하므로 A는 차단되고 A는 r1을 보유하므로 B는 차단됩니다. 각 프로세스는 차단되므로 다른 프로세스가 원하는 리소스를 해제 할 수 없으므로 교착 상태가 발생합니다.
mah

2
트랜잭션 메모리와 관련하여 교착 상태와 라이브 록을 보여주는 훌륭한 비디오가 있습니다. youtube.com/watch?v=_IxsOEEzf-c
BlackVegetable

78

라이브 록

스레드는 종종 다른 스레드의 동작에 응답하여 작동합니다. 다른 스레드의 동작이 다른 스레드의 동작에 대한 응답 인 경우 라이브 록이 발생할 수 있습니다.

교착 상태와 마찬가지로 라이브 록 스레드는 추가 진행을 할 수 없습니다 . 그러나 스레드는 차단되지 않습니다 . 작업을 재개하기 위해 단순히 서로 너무 바빠서 응답합니다 . 이것은 복도에서 서로를지나 가려고하는 두 사람과 비교할 수 있습니다. 그들이 여전히 서로를 차단하고 있음을 알면서, Alphonse는 오른쪽으로 움직이고 Gaston은 왼쪽으로 움직입니다. 그들은 여전히 ​​서로를 차단하고 있습니다.

라이브 록교착 상태 의 주요 차이점은 스레드가 차단되지 않고 계속해서 서로 응답하려고한다는 것입니다.

이 이미지에서 두 원 (스레드 또는 프로세스)은 왼쪽과 오른쪽으로 이동하여 서로에게 공간을 제공하려고합니다. 그러나 그들은 더 이상 이동할 수 없습니다.

여기에 이미지 설명을 입력하십시오



1
이것에는 이름이 있습니다. 아마도 속어일지도 모릅니다 : schlumperdink : P
John Red

64

여기의 모든 내용과 예제는

운영 체제 : 내부 및 설계 원칙
William Stallings
8º Edition

교착 상태 : 둘 이상의 프로세스가 진행될 수없는 상황입니다.

예를 들어 P1과 P2의 두 프로세스와 R1과 R2의 두 리소스를 고려하십시오. 각 프로세스가 기능의 일부를 수행하기 위해 두 자원 모두에 액세스해야한다고 가정하십시오. 그런 다음 OS에서 R1을 P2에, R2를 P1에 할당 할 수 있습니다. 각 프로세스는 두 가지 리소스 중 하나를 기다리고 있습니다. 다른 리소스를 획득하고 두 리소스가 필요한 기능을 수행 할 때까지 이미 소유 한 리소스를 해제하지 않습니다. 두 프로세스는 교착 상태입니다

Livelock : 유용한 작업을 수행하지 않고 두 개 이상의 프로세스가 다른 프로세스의 변경에 응답하여 상태를 지속적으로 변경하는 상황 :

기아 : 스케줄러가 실행 가능한 프로세스를 무기한 간과하는 상황. 진행할 수는 있지만 결코 선택되지는 않습니다.

3 개의 프로세스 (P1, P2, P3)가 각각 자원 R에 주기적으로 액세스해야한다고 가정하십시오. P1이 자원을 소유하고 있고 P2와 P3이 지연되어 해당 자원을 기다리는 상황을 고려하십시오. P1이 중요 섹션을 종료하면 P2 또는 P3에 R에 대한 액세스가 허용되어야합니다. OS가 P3에 대한 액세스 권한을 부여하고 P3이 중요 섹션을 완료하기 전에 P1에 다시 액세스해야한다고 가정하십시오. P3이 완료된 후 OS가 P1에 대한 액세스 권한을 부여한 다음 P1 및 P3에 대한 액세스 권한을 교대로 부여하면 교착 상태 상황이 없어도 P2가 자원에 대한 액세스를 무기한으로 거부 할 수 있습니다.

부록 A-일관성있는 주제

교착 상태 예

while 문을 실행하기 전에 두 프로세스 모두 플래그를 true로 설정하면 각 프로세스는 다른 프로세스가 임계 섹션에 들어가 교착 상태를 일으킨 것으로 생각합니다.

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

라이브 록 예

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

[...] 다음과 같은 일련의 이벤트를 고려하십시오.

  • P0은 플래그 [0]을 true로 설정합니다.
  • P1은 플래그 [1]을 true로 설정합니다.
  • P0은 플래그 [1]을 확인합니다.
  • P1은 플래그 [0]을 확인합니다.
  • P0은 플래그 [0]을 false로 설정합니다.
  • P1은 플래그 [1]을 false로 설정합니다.
  • P0은 플래그 [0]을 true로 설정합니다.
  • P1은 플래그 [1]을 true로 설정합니다.

이 순서는 무기한으로 확장 될 수 있으며 어떤 프로세스도 중요한 섹션으로 들어갈 수 없습니다. 엄밀히 말하면, 두 프로세스의 상대 속도를 변경하면이 사이클이 중단되고 하나가 임계 섹션에 들어갈 수 있기 때문에 교착 상태아닙니다 . 이 조건을 라이브 록 이라고합니다 . 교착 상태는 일련의 프로세스가 중요한 섹션에 들어가기를 원하지만 프로세스가 성공할 수 없을 때 발생합니다. livelock을 사용하면 성공할 수있는 실행 시퀀스가 ​​있지만 프로세스가 중요한 섹션에 들어 가지 않는 하나 이상의 실행 시퀀스를 설명 할 수도 있습니다.

더 이상 책의 내용이 아닙니다.

스핀 락은 어떻습니까?

Spinlock은 OS 잠금 메커니즘의 비용을 피하는 기술입니다. 일반적으로 다음을 수행합니다.

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

beginLock()보다 비용이 많이 들면 문제가 발생하기 시작합니다 doSomething(). 매우 과장된 용어로 비용이 1 초 beginLock이지만 doSomething1 밀리 초에 불과할 때 어떤 일이 발생하는지 상상해보십시오 .

이 경우 1 밀리 초를 기다리면 1 초 동안 방해받지 않습니다.

beginLock그렇게 많은 비용이 듭니까? 잠금이 비어 있으면 비용이 많이 들지 않지만 ( https://stackoverflow.com/a/49712993/5397116 참조 ) 잠금이 해제되지 않으면 OS가 스레드를 "동결"시켜 깨우는 메커니즘을 설정합니다 잠금이 해제되면 나중에 다시 깨 웁니다.

이 모든 것은 잠금을 확인하는 일부 루프보다 훨씬 비쌉니다. 그것이 때때로 "스핀 록"을하는 것이 더 나은 이유입니다.

예를 들면 다음과 같습니다.

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

구현이 신중하지 않으면 모든 CPU를 잠금 메커니즘에 사용하면서 라이브 락에 빠질 수 있습니다.

참조 :

https://preshing.com/20120226/roll-your-own-lightweight-mutex/
스핀 락 구현이 정확하고 최적입니까?

요약 :

교착 상태 : 아무도 진행하지 않고 아무것도하지 않는 상황 (수면, 대기 등). CPU 사용량이 적습니다.

Livelock : 아무도 진행하지 않지만 CPU는 계산이 아닌 잠금 메커니즘에 소비됩니다.

기아 : 한 명의 프로세서가 절대로 기회를 얻지 못하는 상황; 순수한 불운이나 그 재산의 일부 (예를 들어 우선 순위가 낮음);

Spinlock : 잠금이 해제되기를 기다리는 비용을 피하는 기술.


선생님, 교착 상태에 대한 예는 실제로 Spinlock의 예입니다. 교착 상태는 준비 또는 실행 상태가 아니고 일부 자원을 기다리는 프로세스 세트가 차단 될 때 발생합니다. 그러나이 예에서 각각은 몇 가지 작업을 수행하고 있습니다. 즉, 조건을 반복해서 확인합니다. 내가 틀렸다면 나를 바로 잡으십시오.
Vinay Yadav

이 해석의 가능성을 열어 줄 수있는 예는 매우 적습니다. 그래서 나는 그것들의 차이점에 대해 좀 더 명백하게 표현했습니다. 희망이 도움이됩니다.
Daniel Frederico Lins Leite

spinlocks에 대한 설명을 추가해 주셔서 감사합니다. spinlocks는 기술이며이를 정당화하고 이해했습니다. 그러나 한 프로세스 P1이 중요 섹션에 있고 다른 우선 순위가 높은 프로세스 P2가 P1을 선점하도록 예약 된 경우이 우선 순위 반전 문제는 어떻습니까?이 경우 CPU는 P2이고 동기화 메커니즘은 P1입니다. P1이 준비 상태에 있고 P2가 실행 상태에 있으므로 이것을 스핀 록이라고 합니다. 여기서 spinlock은 문제입니다. 내가 일을 제대로 받고 있습니까? 나는 복잡한 것을 제대로 얻을 수 없습니다. 도와주세요
Vinay Yadav

귀하에게 제 제안은 문제를보다 명확하게 나타내는 다른 질문을 작성하는 것입니다. 이제 "사용자 공간"에 있고 P1이 무한 루프로 구현 된 SpinLock으로 보호되는 중요한 세션 내에 있고 선점 된 경우; 그런 다음 P2가 입력을 시도하고 실패하고 모든 시간 조각을 태울 것입니다. 라이브 록을 생성했습니다 (하나의 CPU는 100 %입니다). (잘못 사용하면 spinlock으로 동기화 IO를 보호하는 것입니다.이 예제를 쉽게 시도 할 수 있습니다.) "커널 공간"에서이 메모가 도움이 될 수 있습니다. lxr.linux.no/linux+v3.6.6/Documentation/…
Daniel Frederico Lins Leite

설명을 주셔서 대단히 감사합니다. 어쨌든, 당신의 대답은 다른 사람들과 달리 상당히 설명적이고 도움이되었습니다
Vinay Yadav

13

이중 자물쇠 교착 상태는 작업이 충족 될 수없는 조건을 무기한으로 기다리는 조건입니다.-작업은 공유 리소스에 대한 독점적 제어 권한을 주장합니다-다른 리소스가 릴리스되기를 기다리는 동안 작업은 리소스를 보유합니다-작업은 리소스를 강제로 보류 할 수 없습니다-순환 대기 조건이 존재

LIVELOCK Livelock 조건은 둘 이상의 작업이 종속되어 일부 리소스를 사용하는 경우 순환 종속성 조건을 발생시켜 해당 작업이 계속 실행되는 우선 순위 수준의 모든 작업이 실행되지 않도록 차단할 수 있습니다 (이러한 우선 순위가 낮은 작업은 기아 상태라고 함)


'라이브 록 된'작업이 '백 오프'지연을 포함하는 리소스 중재 프로토콜을 따르고 그 결과 대부분의 시간을 휴면 상태로 유지하는 경우 다른 작업은 굶주 리지 않습니다.
greggo

8

이 두 예는 교착 상태와 라이브 록의 차이점을 보여줍니다.


교착 상태에 대한 Java 예제 :

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

샘플 출력 :

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 예제 :


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

샘플 출력 :

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

두 예제 모두 스레드가 다른 순서로 잠금을 요구하도록합니다. 교착 상태가 다른 잠금을 기다리는 동안 라이브 잠금은 실제로 기다리지 않습니다. 잠금을 얻을 기회없이 필사적으로 잠금을 획득하려고합니다. 모든 시도는 CPU주기를 소비합니다.


코드가 좋습니다. 그러나 라이브 락 예제는 좋지 않습니다. 스레드가 값에서 차단되는지 또는 값 변경을 폴링하는지 여부는 개념적으로 다릅니다. 라이브 잠금을 더 잘 설명하기위한 쉬운 변경은 스레드 A와 B가 필요한 두 번째 잠금을 얻을 수 없다는 사실을 알게되면 잠금을 해제하는 것입니다. 그런 다음 그들은 각각 잠을 자고 원래 가지고 있던 자물쇠를 다시 얻은 다음 다시 잠을 자고 다른 자물쇠를 다시 얻으려고 시도합니다. 따라서 각각의주기는 1) 획득-광산, 2) 수면, 3) 기타 & 실패 획득, 4) 방출-광산, 5) 수면, 6) 반복입니다.
CognizantApe

1
당신이 생각하는 라이브 락이 실제로 오랫동안 존재하여 문제를 일으킬 수 있는지 의심합니다. 다음 잠금을 할당 할 수 없을 때 항상 보유한 모든 잠금을 포기하면 더 이상 대기가 없기 때문에 교착 상태 (및 라이브 잠금) 조건 "보유 및 대기"가 누락됩니다. ( en.wikipedia.org/wiki/Deadlock )
mmirwaldt

실제로 우리가 논의하고있는 라이브 잠금이기 때문에 교착 상태가 없습니다. 내가 준 예제는 geeksforgeeks.org/deadlock-starvation-and-livelock , en.wikibooks.org/wiki/Operating_System_Design/Concurrency/… , docs.oracle.com/javase/tutorial/essential
CognizantApe

0

스레드 A와 스레드 B를 모두 상상해보십시오. 둘 다 synchronised동일한 객체에 있고이 블록 안에는 모두 업데이트되는 전역 변수가 있습니다.

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

스레드 A가에 들어갈 때, while루프 잠금을 보유하고, 그것이 어떻게하고 설정하는 것을 수행 commonVartrue. 그런 다음 B가에서 오는에 입력 스레드 while루프 이후 commonVartrue지금,이 잠금을 보유 할 수있다. 그렇게하고 synchronised블록을 실행 하고로 설정 commonVar합니다 false. 이제, 스레드 A가 다시 새로운 CPU 창을 얻을, 그것은 이었다 종료에 대해 while루프를하지만, 스레드 B는 그냥로 설정 한 false주기가 반복 그래서 다시. 스레드는 무언가를 수행하므로 (전통적인 의미에서 차단되지는 않지만) 거의 아무것도하지 않습니다.

라이브 록이 반드시 여기에 표시 될 필요는 없습니다. synchronised블록 실행이 완료 되면 스케줄러가 다른 스레드를 선호한다고 가정합니다 . 대부분의 경우, 나는 그것이 맞기 어려운 기대라고 생각하며 후드 아래에서 일어나는 많은 일에 달려 있습니다.


좋은 예입니다. 또한 동시 컨텍스트에서 항상 원자 적으로 읽고 쓰는 이유를 설명합니다. while 루프가 동기화 블록 내부에 있으면 위의 문제는 발생하지 않습니다.
CognizantApe
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.