bdfgdfg

[OS] 스레드 동기화와 교착 상태(Deadlock) 본문

CS/운영체제

[OS] 스레드 동기화와 교착 상태(Deadlock)

marmelo12 2022. 4. 6. 16:40
반응형

공유자원

멀티스레드 환경에서 스레드는 프로세스의 데이터,힙영역을 공유하게 된다.

여기서 여러 문제점이 발생할 가능성이 생기는데. 예로들어 밑의 코드와 같은 상황.

int num = 0;

void Inc()
{
    for (int i = 0; i < 100'0000; ++i)
        num++;
}
void Des()
{
    for (int i = 0; i < 100'0000; ++i)
        num--;
}
int main()
{
    std::thread t1(Inc);
    std::thread t2(Des);

    t1.join();
    t2.join();

    std::cout << num;

    return 0;
}

전역변수 num은 데이터 영역에 올라간다. 즉 스레드끼리 공유하는 변수.

해당 공유변수를 대상으로 쓰레드 t1은 백만번 더하고 t2는 백만번 빼는 연산을 진행한다.

스레드에 대한 이해도가 없다면 결과는 0이라 생각하지만 결과는

예상치 못한 결과가 나온다. 심지어 매번 실행할때마다 그 결과가 다르다.

이렇게 둘 이상의 쓰레드가 공유변수에 접근해 발생할 수 있는 문제를 경쟁 상태(race condition)이라 한다.

 -> t1쓰레드에서 num++를 진행하고 결과를 메인 메모리에 반영하기전에 t2쓰레드로 컨텍스트 스위칭이 일어나(혹은 동시실행) num--의 결과를 저장. 다시 t1쓰레드로 컨텍스트 스위칭이 되어 이전에 마저 못한 작업을 처리해 메인 메모리에 이어서 반영.

 -> 즉 제대로 연산이 되지 않는다.

 

이렇게 공유자원에 접근하는 코드(해당 영역을 임계영역(Critical Section)이라한다)에서는 하나의 쓰레드가 공유변수에 대한 작업이 끝날때까지 다른 스레드에서 접근하지 못하도록 동기화 처리가 필요하다.

 -> 하나의 쓰레드가 임계영역에 진입했을 때 다른 쓰레드는 들어가지 못하도록 상호 배제(mutual exclustion)가 필요하다.

 -> 동기화 객체로는 유저모드에서 CriticalSection,SpinLock,mutex(c++11), 커널모드의 mutex,Semapore,Event등이 있다.

즉 잠금(lock)매커니즘을 이용.

 

여기서 간단하게 bool값을 이용해서 bool변수가 true면 진입하지 못하도록 막으면 되지 않을까?라는 생각이 들 수 있지만, 제대로 동작하지 않을 가능성이 높다.

예로들어 lock이 걸려있지 않은 임계구역에 진입하기 위해 lock을 true로 바꾸고 진입하려는 순간 타임아웃(타임 슬라이스를 다 사용)으로 컨텍스트 스위칭이 일어나고, 다른 스레드에서 해당 임계구역의 lock이 false인것을 확인하고 진입. 다시 기존의 스레드로 돌아와서 이어서 작업.

보면 알겠지만 결국 둘 다 임계영역에 진입해버리는 상황이 발생해버린다는 것.

 

화장실에 비유해보자면 두 사람이 화장실 변기칸의 문이 잠겨있지 않은것을 확인하고 동시에 진입해서 문을 잠궈버린 상황이다.

문이 열려있는지 확인(compare)하고 들어가서 문을 잠구는 행위(swap)가 한번에(원자적으로) 일어나야 한다.

 -> CAS함수를 이용. 이에대한 자세한 내용은 https://marmelo12.tistory.com/320?category=1010557

 

 

교착 상태(Deadlock)

교착 상태란 둘 이상의 스레드가 서로의 작업이 끝나기만을 기다리며 작업을 더 이상 진행하지 못하는 상태를 의미한다.

 

교착 상태 필요조건

교착상태는 여러가지 조건에 의해 발생한다.

1. 상호배제

2. 비선점

3. 점유와 대기

4. 원형 대기

위의 4가지를 모두 충족해야 교착상태는 발생한다. (단 하나라도 충족하지 않으면 발생하지 않음)

 

상호 배제

 - 한 스레드가 접근하는 공유자원은 다른 스레드가 접근하지 못하도록 막아야 한다. 해당 임계영역을 하나의 스레드만접근할 수 있도록 동기화를 해주어야하며 다른 스레드가 동시에 사용할 수 없기에 교착 상태가 발생할 수 있다.

 

비선점

 - 한 스레드가 사용중인 동기화 객체(자원)는 중간에 다른 스레드가 빼앗을 수 있는 선점자원이 아니다. 해당 자원을 빼앗을 수 없다면 공유할수도 없기에 교착 상태가 발생할 수 있다.

 

점유와 대기

 - 스레드가 어떤 동기화 객체(자원)를 할당받은 상태에서 다른 동기화 객체(자원)를 기다리는 상태여야 한다.

다른 스레드의 작업 진행을 방해하는 교착 상태가 발생하려면 다른 스레드가 필요로 하는 자원을 점유하고 있으면서 또 다른 자원을 기다리는 상태가 되어야 한다.

 

원형 대기

 - 점유와 대기를 하는 스레드간의 관계가 원을 이루어야 한다.

스레드가 특정 자원에 대해 점유와 대기를 한다고 해서 모두 교착상태에 빠지는 것은 아니고 점유와 대기를 하는 스레드들이 서로 방해하는 방향이 원을 이루면 서로 양보하지 않기에 교착 상태에 빠진다.

 

교착 상태 해결방법

교착 상태를 해결하는 방법은 예방,회피,검출이다. 추가적으로 교착 상태가 발견된 후에 자원을 회복하는 방법도 존재.

 

교착 상태 예방 : 교착 상태를 유발하는 네가지 조건을 무력화한다.

 - 상호배제 예방 (사실상 어렵다. 임계구역의 보호를 위해 상호배제를 하는 것인데, 이것을 제외한다는 것은 말이 안된다.)

 - 비선점 예방 (임계구역을 보호하는 동기화 자원을 뺏을 수 있다면 결국 여러가지 문제가 발생한다.)

 - 점유와 대기 예방 (스레드가 자원을 점유한 상태에서 다른 자원을 기다리지 못하도록 막는 방법. 즉 적어도 atomic으로  처리한다면 전부 처리했거나 혹은 아예 못했거나 둘 중하나의 상황을 적용. 그렇다면 점유와 대기의 방식은 해결할 수 있다. 하지만, 보통 lock을 거는 상황은 임계구역. 즉 둘 이상의 쓰레드가 동시에 접근했을 때 문제가 발생할 수 있는 코드영역이므로 atomic과 같은 처리는 힘들다.)

 - 원형 대기 에방 (순환구조 예방. 자원을 한 방향으로만 사용하도록 설정함으로써 원형 대기를 예방.

모든 자원에 숫자를 부여하고 숫자가 큰 방향으로만 자원을 할당한다. 숫자가 작은 자원을 잡은 상태에서 큰 숫자를 잡는 것은 허용하지만 숫자가 큰 자원을 잡은 상태에서 작은 숫자를 잡는것은 허용x

 -> 다만 숫자를 어떻게 부여할것인지?에 대한 것이 가장 큰 문제. 콘텐츠 코드도 계속해서 늘어날 것인데, 그 미래를 모두 예상하고 이에 대한 처리를 하는것은 사실상 힘들다.

 

 

교착 상태 회피 : 교착 상태가 발생하지 않는 수준으로 자원을 할당한다.

교착 상태 검출 : 자원 할당 그래프를 사용하여 교착 상태를 발견한다.

교착 상태 회복 : 교착 상태를 검출한 후 해결한다.

 

반응형

'CS > 운영체제' 카테고리의 다른 글

[OS] 가상 메모리  (0) 2022.04.09
[OS] 스케줄링 알고리즘과 우선순위  (0) 2022.04.04
[OS] 프로세스와 쓰레드  (0) 2022.04.03
[OS] 컴퓨터 구조 개요  (0) 2022.03.30
[OS] 운영체제 개요  (0) 2022.03.30
Comments