멀티 스레드 응용 프로그램을 작성할 때 가장 일반적인 문제 중 하나는 교착 상태입니다.
커뮤니티에 대한 나의 질문은 다음과 같습니다.
교착 상태 란 무엇입니까?
당신은 그들을 어떻게 감지합니까?
당신은 그들을 처리합니까?
그리고 마지막으로, 당신은 그것들이 발생하는 것을 어떻게 방지합니까?
멀티 스레드 응용 프로그램을 작성할 때 가장 일반적인 문제 중 하나는 교착 상태입니다.
커뮤니티에 대한 나의 질문은 다음과 같습니다.
교착 상태 란 무엇입니까?
당신은 그들을 어떻게 감지합니까?
당신은 그들을 처리합니까?
그리고 마지막으로, 당신은 그것들이 발생하는 것을 어떻게 방지합니까?
답변:
잠금 여러 프로세스가 동시에 같은 리소스에 액세스하려고 할 때 발생합니다.
한 프로세스가 손실되고 다른 프로세스가 완료 될 때까지 기다려야합니다.
교착 상태가 대기 프로세스가 여전히 이전의 첫 번째 요구를 완료 할 수있는 다른 리소스에 들고 때 발생합니다.
예를 들면 다음과 같습니다.
자원 A와 자원 B는 프로세스 X와 프로세스 Y에서 사용됩니다.
교착 상태를 피하는 가장 좋은 방법은 프로세스가 이런 식으로 교차하지 않도록하는 것입니다. 가능한 한 많은 것을 잠글 필요가 없습니다.
데이터베이스에서 단일 트랜잭션으로 다른 테이블을 많이 변경하지 않도록 트리거를 피하고 가능한 한 낙관적 / 더러운 / 노락 읽기로 전환하십시오.
범죄 영화의 교착 상태 상황에 대한 실제 (실제 아님) 예를 설명하겠습니다. 범죄자가 인질을 보유하고 있고 경찰이 범죄의 친구 인 인질을 보유하고 있다고 상상해보십시오. 이 경우 경찰이 친구를 보내지 않으면 범죄자가 인질을 보내지 않을 것입니다. 또한 경찰은 범죄자가 인질을 풀지 않는 한 범죄의 친구를 보내지 못하게 할 것입니다. 이것은 양측이 서로 첫 발을 내딛기 때문에 끝없는 신뢰할 수없는 상황입니다.
따라서 두 스레드에 두 개의 서로 다른 리소스가 필요하고 각 스레드에 다른 리소스가 필요한 경우 교착 상태입니다.
당신은 소녀와 데이트를하고 논쟁 후 언젠가 양측이 서로에게 상처를 입히고 미안하고 부재중 전화를 기다리고 있습니다 . 이 상황에서 양측이 상대방 중 하나가 I-am- 미안 전화를 받는 경우에만 서로 통신을 원합니다 . 둘 다 통신을 시작하지 않고 수동 상태로 대기하지 않기 때문에 둘 다 상대방이 교착 상태 상황에서 통신을 시작하기를 기다립니다.
교착 상태를 정의하기 위해 먼저 프로세스를 정의합니다.
프로세스 : 우리가 아는 것처럼 프로세스는 program
실행 중일뿐입니다.
리소스 : 프로그램 프로세스를 실행하려면 몇 가지 리소스가 필요합니다. 리소스 범주에는 메모리, 프린터, CPU, 열린 파일, 테이프 드라이브, CD-ROM 등이 포함됩니다.
교착 상태 : 교착 상태는 둘 이상의 프로세스가 일부 자원을 보유하고 더 많은 자원을 확보하려고 할 때 상황 또는 조건이며 실행이 완료 될 때까지 자원을 해제 할 수 없습니다.
교착 상태 또는 상황
위의 다이어그램에는 두 개의 프로세스 P1 과 p2 가 있으며 두 개의 리소스 R1 과 R2가 있습니다.
리소스 R1 은 프로세스 P1에 할당 되고 리소스 R2 는 프로세스 p2에 할당됩니다 . 프로세스 P1의 실행을 완료하려면 리소스 R2가 필요 하므로 P1 은 R2를 요청 하지만 R2 는 이미 P2에 할당되어 있습니다.
같은 방법으로 프로세스 P2 가 실행을 완료하려면 R1이 필요 하지만 R1 은 이미 P1에 할당되어 있습니다.
두 프로세스 모두 실행을 완료 할 때까지는 리소스를 해제 할 수 없습니다. 그래서 둘 다 다른 자원을 기다리고 있으며 영원히 기다릴 것입니다. 이것이 DEADLOCK 조건입니다.
교착 상태가 발생하려면 네 가지 조건이 충족되어야합니다.
이 모든 조건은 위 다이어그램에서 만족됩니다.
교착 상태는 스레드가 절대 발생하지 않는 것을 기다리고있을 때 발생합니다.
일반적으로 스레드가 이전 소유자가 릴리스하지 않은 뮤텍스 또는 세마포어를 기다리는 경우에 발생합니다.
또한 다음과 같이 두 개의 스레드와 두 개의 잠금이 관련된 상황이 발생할 때 자주 발생합니다.
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
일반적으로 예상되는 작업이 수행되지 않거나 응용 프로그램이 완전히 중단되기 때문에이를 감지합니다.
교착 상태 섹션 에서이 멋진 기사를 살펴볼 수 있습니다 . C #에 있지만 아이디어는 여전히 다른 플랫폼에서도 동일합니다. 나는 쉽게 읽을 수 있도록 여기에 인용한다.
교착 상태는 두 스레드가 각각 다른 스레드가 보유한 자원을 기다릴 때 발생하므로 어느 쪽도 진행할 수 없습니다. 이것을 설명하는 가장 쉬운 방법은 두 개의 자물쇠를 사용하는 것입니다.
object locker1 = new object();
object locker2 = new object();
new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2); // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1); // Deadlock
}
교착 상태는 OS의 다중 처리 / 다중 프로그래밍 문제에서 공통적 인 문제입니다. 두 개의 프로세스 P1, P2 및 두 개의 공유 가능한 자원 R1, R2가 있으며 중요한 섹션에 두 자원 모두에 액세스해야한다고 가정하십시오.
초기에 OS는 R1을 프로세스 P1에 할당하고 R2를 프로세스 P2에 할당합니다. 두 프로세스가 동시에 실행 중이므로 코드 실행이 시작될 수 있지만 프로세스가 중요 섹션에 도달하면 문제가 발생합니다. 따라서 프로세스 R1은 프로세스 P2가 R2를 릴리스 할 때까지 대기하고 그 반대도 마찬가지입니다. 따라서 그들은 영원히 대기합니다 (DEADLOCK CONDITION).
작은 아날로그 ...
당신의 어머니 (OS),
당신 (P1),
당신의 형제 (P2),
사과 (R1),
칼 (R2),
비판 (칼로 사과 자르기).당신의 어머니는 처음에 당신에게 형제에게 사과와 칼을줍니다.
둘 다 행복하고 놀고 있습니다 (코드 실행).
누구든지 어느 시점에서 사과 (중요 부분)를 자르기를 원합니다.
당신은 당신의 형제에게 사과를주고 싶지 않습니다.
당신의 형제는 당신에게 칼을주고 싶지 않습니다.
그래서 둘 다 오랫동안 아주 오래 기다릴 것입니다 :)
교착 상태는 두 스레드가 잠금을 요구할 때 발생하여 두 스레드 중 하나가 진행되지 못하게합니다. 이를 피하는 가장 좋은 방법은 신중한 개발입니다. 많은 임베디드 시스템은 워치 독 타이머 (시스템이 특정 시간 동안 중단 될 때마다 시스템을 재설정하는 타이머)를 사용하여 시스템을 보호합니다.
교착 상태 상황 을 이해하기위한 고전적이고 매우 간단한 프로그램 :-
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}
메인 스레드가 Lazy.main을 호출하면 Lazy 클래스가 초기화되었는지 확인하고 클래스 초기화를 시작합니다. 메인 스레드는 이제 initialized를 false로 설정하고 run 메소드가 initialized를 true로 설정하는 백그라운드 스레드를 작성하고 시작하며 백그라운드 스레드가 완료되기를 기다립니다.
이번에는 클래스가 현재 다른 스레드에 의해 초기화되고 있습니다. 이러한 상황에서 백그라운드 스레드 인 현재 스레드는 초기화가 완료 될 때까지 클래스 객체를 기다립니다. 불행하게도, 메인 스레드 인 초기화를 수행하는 스레드는 백그라운드 스레드가 완료되기를 기다리고 있습니다. 두 스레드가 서로 기다리고 있기 때문에 프로그램은 DEADLOCKED입니다.
교착 상태는 단일 프로세스 / 스레드가 조치를 실행할 수없는 시스템의 상태입니다. 다른 사람들이 언급했듯이 교착 상태는 일반적으로 각 프로세스 / 스레드가 다른 프로세스 (또는 동일한 프로세스) / 스레드에 의해 이미 잠겨있는 리소스에 대한 잠금을 획득하려는 상황의 결과입니다.
그들을 찾아 피하는 방법에는 여러 가지가 있습니다. 하나는 매우 열심히 생각하거나 많은 것을 시도하고 있습니다. 그러나 병렬 처리를 다루는 것은 매우 어렵고 대부분의 사람들은 문제를 완전히 피할 수 없습니다.
이러한 종류의 문제를 처리하는 데 진지한 경우 좀 더 공식적인 방법이 유용 할 수 있습니다. 내가 아는 가장 실용적인 방법은 프로세스 이론적 접근법을 사용하는 것입니다. 여기에서는 프로세스 언어 (예 : CCS, CSP, ACP, mCRL2, LOTOS)로 시스템을 모델링하고 사용 가능한 도구를 사용하여 교착 상태 (및 기타 속성도)를 (모델-) 확인합니다. 사용할 도구 세트의 예로는 FDR, mCRL2, CADP 및 Uppaal이 있습니다. 일부 용감한 영혼은 순전히 상징적 인 방법을 사용하여 자신의 시스템 교착 상태를 증명할 수도 있습니다 (정리 증명; Owicki-Gries 찾기).
그러나 이러한 공식적인 방법에는 일반적으로 약간의 노력이 필요합니다 (예 : 프로세스 이론의 기본 사항 배우기). 그러나 그것은 단순히 이러한 문제가 어렵다는 사실의 결과라고 생각합니다.
위의 설명은 훌륭합니다. 이것이 유용 할 수 있기를 바랍니다 : https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
데이터베이스에서 세션 (예 : ora)이 다른 세션 (예 : 데이터)이 보유한 리소스를 원하지만 해당 세션 (데이터)도 첫 번째 세션 (오라)이 보유한 리소스를 원합니다. 세션이 두 개 이상있을 수도 있지만 아이디어는 동일합니다. 실제로 교착 상태는 일부 트랜잭션이 계속 작동하지 못하게합니다. 예를 들어, ORA-DATA가 잠금 A를 보유하고 잠금 B를 요청하고 SKU가 잠금 B를 보유하고 잠금 A를 요청한다고 가정하십시오.
감사,
교착 상태는 잠금과 함께 발생하는 것이 아니라 가장 흔한 원인입니다. C ++에서는 std :: thread 객체에 대해 각 스레드 호출 join ()을 사용하여 두 개의 스레드로 잠금없이 교착 상태를 만들 수 있습니다.
공유 자원에 대한 액세스를 제어하기 위해 잠금을 사용하면 교착 상태가 발생하기 쉽고 트랜잭션 스케줄러만으로는 발생을 막을 수 없습니다.
예를 들어, 관계형 데이터베이스 시스템은 다양한 잠금을 사용하여 트랜잭션 ACID 특성 을 보장합니다 .
사용중인 관계형 데이터베이스 시스템에 관계없이 특정 테이블 레코드를 수정 (예 : UPDATE
또는 DELETE
) 할 때 항상 잠금이 획득됩니다 . 현재 실행중인 트랜잭션에 의해 수정 된 행을 잠그지 않으면 Atomicity가 손상 됩니다.
이 기사 에서 설명했듯이 교착 상태는 다음 다이어그램과 같이 두 트랜잭션이 서로를 잠금 해제 할 때까지 대기하기 때문에 두 개의 동시 트랜잭션이 진행될 수 없을 때 교착 상태가 발생합니다.
두 트랜잭션이 모두 잠금 획득 단계이므로 다음 트랜잭션을 획득하기 전에 어느 것도 잠금을 해제하지 않습니다.
잠금에 의존하는 동시성 제어 알고리즘을 사용하는 경우 항상 교착 상태 상황에서 실행될 위험이 있습니다. 교착 상태는 데이터베이스 시스템뿐만 아니라 모든 동시성 환경에서도 발생할 수 있습니다.
예를 들어, 멀티 스레딩 프로그램은 두 개 이상의 스레드가 이전에 획득 한 잠금을 대기하여 스레드가 진행될 수 없도록 교착 상태를 일으킬 수 있습니다. Java 애플리케이션에서 이러한 상황이 발생하면 JVM은 스레드가 강제로 실행을 중지하고 잠금을 해제하도록 할 수 없습니다.
Thread
클래스가 stop
메소드를 노출 하더라도 해당 메소드는 스레드가 중지 된 후 오브젝트가 불일치 상태로 남을 수 있으므로 Java 1.1 이후 더 이상 사용되지 않습니다. 대신, Java는 interrupt
인터럽트를받는 스레드가 단순히 인터럽트를 무시하고 실행을 계속할 수있는 힌트 역할을 하는 메소드를 정의합니다 .
이러한 이유로 Java 응용 프로그램은 교착 상태 상황에서 복구 할 수 없으며 교착 상태가 발생하지 않도록 잠금 획득 요청을 주문하는 것은 응용 프로그램 개발자의 책임입니다.
그러나 데이터베이스 시스템은 특정 트랜잭션이 추가로 획득하려는 다른 잠금을 예측할 수 없으므로 주어진 잠금 획득 순서를 시행 할 수 없습니다. 잠금 순서를 유지하는 것은 데이터 액세스 계층의 책임이되며 데이터베이스는 교착 상태 상황을 복구하는 데 도움을 줄 수 있습니다.
데이터베이스 엔진은 교착 상태로 인한 잠금 대기주기에 대한 현재 충돌 그래프를 스캔하는 별도의 프로세스를 실행합니다. 주기가 감지되면 데이터베이스 엔진이 하나의 트랜잭션을 선택하고 중단하여 잠금이 해제되어 다른 트랜잭션이 진행될 수 있습니다.
JVM과 달리 데이터베이스 트랜잭션은 원자 단위 작업으로 설계되었습니다. 따라서 롤백은 데이터베이스를 일관된 상태로 둡니다.
이 주제에 대한 자세한 내용은 이 기사 도 확인하십시오 .
본질적으로 Mutex는 공유 리소스에 대한 보호 된 액세스를 제공하는 잠금입니다. Linux에서 스레드 뮤텍스 데이터 유형은 pthread_mutex_t입니다. 사용하기 전에 초기화하십시오.
공유 리소스에 액세스하려면 뮤텍스를 잠 가야합니다. 뮤텍스가 이미 잠금 상태 인 경우 호출은 뮤텍스가 잠금 해제 될 때까지 스레드를 차단합니다. 공유 리소스 방문이 완료되면 잠금 해제해야합니다.
전반적으로 몇 가지 기록되지 않은 기본 원칙이 있습니다.
공유 자원을 사용하기 전에 잠금을 확보하십시오.
잠금 장치를 최대한 짧게 유지하십시오.
스레드가 오류를 반환하면 잠금을 해제하십시오.