CH12) 프로세스 동기화 - 2

Jobmania·2023년 7월 20일
0

운영체제

목록 보기
8/13
post-thumbnail

12-02) 동기화 기법

프로세스를 동기화하지 않으면 코드가 예기치 않게 동작할 수 있다... 그래서 동기화를 위한 기법들이 있음.

  • 뮤텍스 락
  • 세마포
  • 모니터

1. 뮤텍스 락 (Mutex lock)

상호 배제를 위한 동기화 도구(자물쇠 역할),

마치 자물쇠의 역할을 수행. == 뮤텍스락, 상호 배제를 위한 동기화 도구
단순한 형태 : 전역 변수 하나, 함수 두개

  • 자물쇠 역할 : 프로세스들이 공유하는 전역변수 lock
  • 임계 구역을 잠그는 역할 : acquire 함수
  • 임계 구역의 잠금을 해제하는 역할 : release 함수
public class MutexExample {
    private static Boolean lock = false; // 공유하는 락 객체

    void acquire() {
        synchronized (lock) { // synchronized 블록으로 임계 구역에 진입
            // 임계 구역에 들어가기 전에 필요한 초기화나 확인 작업 수행
            
            // 락이 해제될 때까지 기다림 (lock이 false일 때)
            while (lock) {
                
                }
            }
            lock = true; // 락을 획득
        }
    }

    void release() {
        synchronized (lock) {
            // 임계 구역을 빠져나간 후에 필요한 정리 작업 수행
            lock = false; // 락을 해제
          
        }
    }
}
acquire(); // 잠겨있는지 확인, 잠겨있지 않다면 잠그고 들어가기
// 임계구역 진입 ( 이전 장의 '총합' 변수 접근)
release(); // 자물쇠 반환!

syncronized라는 키워드가 붙었습니다. syncronized로 선언된 메서드를 호출하기 위해서는 메서드를 실행하기 위한 락을 획득해야 합니다.
만일 락을 획득할 수 없다면 (다른 스레드가 syncronized 메서드를 실행 중에 있다면) 락을 획득하지 못한 스레드는 그대로 대기 상태가 됩니다.
syncronized 메서드를 실행하는 스레드가 메서드 실행을 종료하면 락이 해제되고, 대기 상태에 있던 스레드가 깨어나 실행을 재개할 수 있게 됩니다.

acquire 함수

  • 프로세스가 임계 구역에 진입하기 전에 호출
  • 임계구역이 잠겨있다면 -> 임계 구역이 열릴때 까지(lock이 false가 될때까지 ) 임계구역을 반복적으로 확인
  • 임계구역이 열려 있따면 -> 임계 구역을 잠그기(lock을 true로 바꾸기)

release 함수

  • 임계 구역에서의 작업이 끝나고 호출
  • 잠긴 임계구역 열기( true -> false로 바꾸기)

바쁜 대기 (busy waiting)

 // 락이 해제될 때까지 기다림 (lock이 false일 때)
            while (lock) {
                try {
                    lock.wait(); // 락을 해제하고 다른 스레드로부터의 알림을 기다림
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
  • 실제로는 c/c++, python 사용자가 직접 구현하지 않아도 뮤텍스락 기능을 제공한다. 보다 더 정교하게 설계되어있음. 실제로 구현할 일이 별로 없음..

2. 세마포(semaphore)

뮤텍스락과 비슷하지만, 조금 더 일반화된 방식의 동기화 도구이다.
뮤텍스락은 공유자원에 대해 하나인 경우, 세마포는 공유자원이 여러개인 경우이다.

세마포 종류에도 이진 세마포(binary semaphore)와 카운팅 세마포가 있다. 이진 세마포는 뮤텍스 락과 비슷한 개념이다.


세마포는 철도 신호기에 유래한 단어이다. 마치 프로세스가 임계구역에 진입할때 신호를 보고 진입여부를 판단한다.

세마포의 단순한 형태 : 전역 변수 하나, 함수 두개

  • 임계 구역에 진입할 수 있는 프로세스의 개수 ( 사용가능한 공유 자원의 개수)를 나타내는 전역 변수 S
  • 임계구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait함수
  • 임계구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'고 신호를 주는 signal 함수
wait()
// 임계구역
signal()
wait(){   // 만일 임계 구역에 진입할 수 있는 프로세스 갯수가 0 이하라면
	while ( S < = 0 ){ 
    }    // 지속 확인
    S--; // 임계 구역에 진입할 수 있는 프로세스 갯수가 하나 이상이면 S를 1 감소 시키고 임계 구역 진입!! 
}
signal(){
	S++ // 임계구역 작업을 끝낸후 S 를 1 증가
}

만약 세개의 프로세스 P1,P2,P3가 두개의 공유자원(S == 2) 에 접근한다고 가정..


1. P1은 wait호출, S==2 이므로 S-- 하고 임계진입
2. P2은 wait호출, S==1 이므로 S-- 하고 임계진입
3. P3은 wait호출, S==0 무한 반복하며 S 확인
4. P1 임계구역 종료, signal()호출, S 1 증가
5. P3가 S가 1이 됨을 확인, S--하고 임계진입

분명히 문제가 있다. 공유자원이 없는 경우 무한히 반복하여 확인하는것 바쁜대기..(Busy waiting)..

바쁜 대기 해결 방법

그래소 실제로는 S<=0일 경우, 즉 사용할 수 있는 자원이 없는 경우 대기상태로 만든다.(해당 프로세스의 PCB를 대기 큐에 삽입 시킴)
이후, 자원이 생겼을 때 대기 큐의 프로세슬르 준비상태로 만듦.
(해당 프로세스 PCB를 대기큐에서 꺼내 준비 큐에 삽입)

    void wait(Process p) {
            S--;
            if (S < 0 ) {
             	addToQueue(p); //	 add this process to Queue, 해당 프로세스를 대기큐에 삽입
                sleep(); // 대기상태로 만든다.
            }
    }

    void signal(Process p) {
    	S++;
       if (S < = 0 ) {
           removeQueue();  // 대기큐에서 제거.
           wakeup(p); // 프로세스를 준비큐로 옮긴다.  
        }
    }


바뀐 로직으로 다시 실행을 수행하면
1. P1은 wait호출 S--, S는 1 이므로 임계진입
2. P2은 wait호출, S--, S는 0 이므로 하고 임계진입
3. P3은 wait호출, S--, S는 -1 이므로 PCB를 대기큐에 넣고 대기전환
4. P1 임계구역 종료, signal()호출, S 1 증가 , S는 0인 상태, P3를 준비 큐로 이동.
5. 깨어난 P3가 임계진입
6. P2 임계구역 종료, signal()호출, S 1 증가 , S는 1인 상태
7. P3 임계구역 종료, signal()호출, S 1 증가 , S는 2인 상태

세마포를 이용한 실행순서 동기화

  • 세마포 변수 S를 0으로 두고,
  • 먼저 실행할 프로세스 뒤에 signal() 함수,
  • 다음에 실행할 프로세스 앞에 wait 함수를 붙이면 된다.

세마포도 뮤텍스 락과 마찬가지로 많은 프로그래밍언어에서 사용

3. 모니터

세마포 처럼 매번 임계구역 앞뒤로 wait(), signal()을 호출해야하나??

그래서 나온것이 모니터
자바에서 활용, 사용자(개발자)가 다루기에 편한 동기화 도구이다.
모니터는 공유자원과 공유자원에 접근하기 위한 인터페이스(통로)를 묶어서 관리한다. 그리고 프로세슨느 반드시 인터페이스를 통해서만 자원에 접근할 수 있다.

상호배제를 위한 동기화

상호 배제를 위한 큐 와 조건 변수에 대한 큐는 다르다
전자는 모니터에 하나만의 프로세스만 진입하도록 만들어진 큐
후자는 모니터에 진입한 프로세스의 실행조건이 만족될때까지 잠시 실행이 중단되어 기다리기 위해 만들어진 큐

  • 인터페이스를 위한 큐
  • 공유자원에 접근하고자 하는 프로세스를 (인터페이스를 위한) 큐에 삽입
  • 큐에 삽입된 순서대로 ( 한번에 하나의 프로세스만 ) 공유 자원 이용
  • 모니터 안에 항상 하나의 프로세스만 들어오도록 동기화를 제공!

실행 순서 제어를 위한 동기화

  • 조건 변수 이용 (condition variable)
    프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수

    • 조건변수.wait() : 대기상태로 변경, 조건변수에 대한 큐에 삽입

    • 조건변수 signal() : wait()로 대기상태로 접어든 조건변수를 실행상태로 변경 :

x.wait()가 호출이되면 조건 변수 x에 대한 큐에 삽입되고 모니터는 비게된다. 이후 다른 프로세스에서 x.signal()을 호출시 x 프로세스는 대기상테에서 준비상태로 변경후 모니터 안으로 들어오게 된다.

이외에도 어떤프로세스가 잠깐 중단후 signal을 불러와 x 프로세스가 끝난후에 signal을 호출 할 수 도 있따.

정리하면, 모니터 안에는 하나의 프로세스만 존재 할 수 있따.

  • wait()를 호출 했던 프로세스는 signal()을 호출한 프로세스가 모니터를 떠난 뒤에 수행을 재개
  • siganl()을 호출한 프로세스의 실행을 일시 중단하고 자신이 실행된 뒤 다시 signal()을 호출한 프로세스의 수행을 재개

모니터는 다음방식으로 실행순서를 제어

  • 특정 프로세스가 아직 실행할 조건이 되지 않았다면 wait를 통해 실행중단.
  • 특정 프로세스가 실행할 조건이 충족 되었다면 signal을 통해 실행 재개

코드 적용 예시

profile
HelloWorld에서 RealWorld로

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

뛰어난 글이네요, 감사합니다.

답글 달기