이전에 프로세스 동기화에 대해서 설명했습니다.

프로세스를 동기화하지 않으면 겉보기엔은 아무런 문제가 없어보이지만 내부에서 예상치 못하게 작동할 수 있습니다.

동기화를 위해 대표적인 도구들이 있습니다.

바로 뮤텍스 락, 세마포어, 모니터입니다.

뮤텍스 락

뮤텍스 락은 동시에 접근해서는 안 되는 자원에 동시에 접근하지 않도록 상호 배제를 위한 동기화 도구입니다.

임계 구역에 진입하는 프로세스는 "나 지금 임계구역에 있어"라는 신호를 알리기 위해 뮤텍스 락을 이용해 임계 구역에 자물쇠를 걸어둘 수 있고, 다른 프로세스는 임계 구역이 잠겨 있다면 기다리고, 잠겨 있지 않다면 임계 구역에 진입할 수 있습니다.

뮤텍스 락은 하나의 전역 변수두 개의 함수로 구현할 수 있습니다.

  • 자물쇠 역할 : 프로세스들이 공유하는 전역 변수 lock (true 또는 false로 나타냄)

  • 임계 구역을 잠그는 역할 : acquire 함수

  • 임계 구역의 잠금을 해제하는 역할 : release 함수

acquire 함수는 프로세스가 임계 구역에 진입하기 전에 호출하는 함수입니다. 임계 구역을 잠겨 있는지, 열려 있는지 반복적으로 확인합니다.

release 함수는 임계 구역에서의 작업이 끝나고 호출하는 함수입니다. 잠긴 임계 구역을 열어주는(true -> false) 함수라고 생각하면 됩니다.

이 뮤텍스 락은 하나의 프로세스만 임계 구역에 지입할 수 있습니다.

acquire 함수는 임계 구역이 잠겨 있을 경우 프로세스는 반복적으로 lock을 확인하는데, 이런 대기 방식을 바쁜 대기(Busy Wait)라고 합니다.

세마포어

세마포어의 사전적 의미로는 역이나 군대에서 사용하는 수신호라는 뜻이며, 뮤텍스 락과 동일하게 동기화를 위해 만들어진 대표적인 도구입니다.

위에서 설명했듯이 뮤텍스 락은 하나의 공유 자원에 접근하는 프로세스를 상정한 방식입니다.

세마포어는 뮤텍스 락과 다르게 공유 자원이 여러 개 있는 상황에서도 적용이 가능한 동기화 도구입니다.

세마포어는 하나의 변수두 개의 함수로 구현할 수 있습니다.

  • 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)를 나타내는 전역 변수 S

  • 임계 구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait 함수

  • 임계 구역 앞에서 기다리는 프로세스에 신호를 주는 signal 함수

세마포어도 마찬가지로 임계 구역 진입 전후로 wait()signal()를 호출합니다.

wait() 함수는 다음과 같이 구현합니다.

wait() {
	while (S <= 0) {
    	;
        S--;
    }
}

사용할 수 있는 자원이 있는지 반복적으로 확인하고, 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소시키고 임계 구역에 진입합니다.

signal() 함수는 다음과 같이 구현합니다.

signal() {
	S++
}

임계 구역에서 작업을 마친 뒤 S를 1 증가시킵니다.

하지만, 뮤텍스 락과 마찬가지로 반복하여 전역 변수 S를 확인하는 바쁜 대기 상태가 발생하는데, 이는 CPU 주기를 낭비한다는 점에서 손해입니다.

그래서 wait() 함수는 만일 사용할 수 있는 자원이 없을 경우 해당 프로세스 상태를 대기 상태로 만들고, 그 프로세스의 PCB를 세마포를 위한 대기 큐에 집어넣습니다.

그리고 다른 프로세스가 임계 구역에서의 작업이 끝나고 signal() 함수를 호출하면 signal() 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 준비 상태로 변경한 뒤 준비 큐로 옮겨주도록 개선한 방식을 사용합니다.

모니터

모니터는 매번 임계 구역에 wait() 함수와 signal() 함수를 명시하는 세마포어가 사용하기 조금 불편한 면이 존재하고, 자칫 잘못된 사용으로 인해 예기치 못한 결과를 얻을 수 있다는 점 때문에 등장한 프로세스 동기화 도구입니다. 세마포어보다 고수준 동기화 기능을 제공합니다.

모니터는 공유 자원 + 공유 자원 접근 함수로 이루어져 있습니다.

또한 2개의 큐를 가지고 있습니다.

각각 상호배타(Mutual Exclusion) 큐, 조건동기(Conditional Synchronization) 큐입니다.

상호배타 큐는 공유 자원에 하나의 프로세스만 접근하도록 하기 위한 큐 입니다.

조건동기 큐는 이미 공유자원을 사용하고 있는 프로세스가 특정한 호출 wait()을 통해 조건동기 큐로 들어갈 수 있습니다.

조건동기 큐에 들어가있는 프로세스는 공유 자원을 사용하고 있는 다른 프로세스가 깨워줄 수 있습니다.

깨워주는 다른 프로세스가 특정한 호출 notify()을 해주며, 깨워주더라도 이미 공유 자원을 사용하고 있는 프로세스가 해당 구역을 나가야 큐에 있던 프로세스가 실행됩니다.

자바는 모니터를 제공하는 대표적인 언어입니다. 자바의 모든 객체는 모니터가 될 수 있습니다.

아래의 코드는 자바에서 모니터를 사용한 예시입니다.

class C {
    private int value,; // 공유변수

    synchronized void f() { // 배타동기
        ... 
    }

    synchronized void g() {
        ... 
    }

    void h() {
        ... 
    }
}

C 클래스는 모니터를 사용하고 있는 클래스입니다.

value 변수는 여러 쓰레드가 공유하고 있는 변수로 볼 수 있으며, synchronized 키워드는 배타동기를 수행하는 함수를 말합니다. 즉, 해당 함수에는 단 하나의 쓰레드만 접근할 수 있습니다.

f() 함수와 g() 함수는 synchronized 키워드를 통해 상호배타 함수로 선언하였는데, 이것은 둘 다 같은 임계구역을 갖는다는 의미입니다.

즉, f() 함수에 한 프로세스가 수행 중이라면, f() 함수뿐 아니라 g() 함수에도 다른 프로세스는 접근할 수 없습니다.

반면에 h() 함수는 일반적인 함수인데, 이 함수에서는 공통 변수에 대한 업데이트를 하지 않는다는 것을 예상할 수 있습니다. (여러 프로세스가 동시에 접근가능)

조건동기는 다음과 같은 특정한 메서드 호출로 사용할 수 있습니다.

  • wait() : 호출한 프로세스를 조건동기 큐에 삽입합니다.

  • notify() : 조건동기 큐에 있는 프로세스를 하나 깨워줍니다.

  • notifyAll() : 조건동기 큐에 있는 모든 프로세스를 깨워줍니다.

이상으로 프로세스 동기화 기법에 대해서 간단히 알아봤습니다.

참고

  • KOCW - 운영체제, 양희재 교수님
  • 혼자 공부하는 컴퓨터구조 + 운영체제
profile
꾸준함으로 성장하는 개발자 지망생

0개의 댓글