답변:
http://en.wikipedia.org/wiki/Deadlock 에서 가져온 것 :
동시 컴퓨팅에서 교착 상태 는 조치 그룹의 각 구성원이 다른 구성원이 잠금을 해제하기를 기다리는 상태입니다.
라이브 락은 라이브 록에 관련된 프로세스의 상태가 지속적으로 서로 관련, 아무것도 진행하지으로 변경하는 것을 제외하고, 교착 상태와 유사하다. Livelock은 특별한 리소스 기아 사례입니다. 일반 정의는 특정 프로세스가 진행 중이 아님을 나타냅니다.
라이브 록의 실제 예는 두 사람이 좁은 복도에서 만났을 때 발생하며, 각 사람은 다른 사람이 지나갈 수 있도록 옆으로 움직여 공 손해 지려고하지만 둘 다 반복적으로 움직이기 때문에 아무런 진전없이 좌우로 흔들립니다. 동시에 같은 방식으로.
라이브 록은 교착 상태를 감지하고 복구하는 일부 알고리즘의 위험입니다. 둘 이상의 프로세스가 조치를 취하면 교착 상태 감지 알고리즘이 반복적으로 트리거 될 수 있습니다. 하나의 프로세스 (임의로 또는 우선 순위로 선택) 만 조치를 수행하여이를 방지 할 수 있습니다.
스레드는 종종 다른 스레드의 동작에 응답하여 작동합니다. 다른 스레드의 동작이 다른 스레드의 동작에 대한 응답 인 경우 라이브 록이 발생할 수 있습니다.
교착 상태와 마찬가지로 라이브 록 스레드는 추가 진행을 할 수 없습니다 . 그러나 스레드는 차단되지 않습니다 . 작업을 재개하기 위해 단순히 서로 너무 바빠서 응답합니다 . 이것은 복도에서 서로를지나 가려고하는 두 사람과 비교할 수 있습니다. 그들이 여전히 서로를 차단하고 있음을 알면서, Alphonse는 오른쪽으로 움직이고 Gaston은 왼쪽으로 움직입니다. 그들은 여전히 서로를 차단하고 있습니다.
라이브 록 과 교착 상태 의 주요 차이점은 스레드가 차단되지 않고 계속해서 서로 응답하려고한다는 것입니다.
이 이미지에서 두 원 (스레드 또는 프로세스)은 왼쪽과 오른쪽으로 이동하여 서로에게 공간을 제공하려고합니다. 그러나 그들은 더 이상 이동할 수 없습니다.
여기의 모든 내용과 예제는
운영 체제 : 내부 및 설계 원칙
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;
[...] 다음과 같은 일련의 이벤트를 고려하십시오.
이 순서는 무기한으로 확장 될 수 있으며 어떤 프로세스도 중요한 섹션으로 들어갈 수 없습니다. 엄밀히 말하면, 두 프로세스의 상대 속도를 변경하면이 사이클이 중단되고 하나가 임계 섹션에 들어갈 수 있기 때문에 교착 상태 가 아닙니다 . 이 조건을 라이브 록 이라고합니다 . 교착 상태는 일련의 프로세스가 중요한 섹션에 들어가기를 원하지만 프로세스가 성공할 수 없을 때 발생합니다. livelock을 사용하면 성공할 수있는 실행 시퀀스가 있지만 프로세스가 중요한 섹션에 들어 가지 않는 하나 이상의 실행 시퀀스를 설명 할 수도 있습니다.
더 이상 책의 내용이 아닙니다.
스핀 락은 어떻습니까?
Spinlock은 OS 잠금 메커니즘의 비용을 피하는 기술입니다. 일반적으로 다음을 수행합니다.
try
{
lock = beginLock();
doSomething();
}
finally
{
endLock();
}
beginLock()
보다 비용이 많이 들면 문제가 발생하기 시작합니다 doSomething()
. 매우 과장된 용어로 비용이 1 초 beginLock
이지만 doSomething
1 밀리 초에 불과할 때 어떤 일이 발생하는지 상상해보십시오 .
이 경우 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 : 잠금이 해제되기를 기다리는 비용을 피하는 기술.
이중 자물쇠 교착 상태는 작업이 충족 될 수없는 조건을 무기한으로 기다리는 조건입니다.-작업은 공유 리소스에 대한 독점적 제어 권한을 주장합니다-다른 리소스가 릴리스되기를 기다리는 동안 작업은 리소스를 보유합니다-작업은 리소스를 강제로 보류 할 수 없습니다-순환 대기 조건이 존재
LIVELOCK Livelock 조건은 둘 이상의 작업이 종속되어 일부 리소스를 사용하는 경우 순환 종속성 조건을 발생시켜 해당 작업이 계속 실행되는 우선 순위 수준의 모든 작업이 실행되지 않도록 차단할 수 있습니다 (이러한 우선 순위가 낮은 작업은 기아 상태라고 함)
이 두 예는 교착 상태와 라이브 록의 차이점을 보여줍니다.
교착 상태에 대한 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를 모두 상상해보십시오. 둘 다 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
루프 잠금을 보유하고, 그것이 어떻게하고 설정하는 것을 수행 commonVar
에 true
. 그런 다음 B가에서 오는에 입력 스레드 while
루프 이후 commonVar
인 true
지금,이 잠금을 보유 할 수있다. 그렇게하고 synchronised
블록을 실행 하고로 설정 commonVar
합니다 false
. 이제, 스레드 A가 다시 새로운 CPU 창을 얻을, 그것은 이었다 종료에 대해 while
루프를하지만, 스레드 B는 그냥로 설정 한 false
주기가 반복 그래서 다시. 스레드는 무언가를 수행하므로 (전통적인 의미에서 차단되지는 않지만) 거의 아무것도하지 않습니다.
라이브 록이 반드시 여기에 표시 될 필요는 없습니다. synchronised
블록 실행이 완료 되면 스케줄러가 다른 스레드를 선호한다고 가정합니다 . 대부분의 경우, 나는 그것이 맞기 어려운 기대라고 생각하며 후드 아래에서 일어나는 많은 일에 달려 있습니다.