Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags more
Archives
Today
Total
관리 메뉴

ultra_dev

프로세스 동기화 본문

혼자 공부하는 컴퓨터구조+운영체제

프로세스 동기화

ultra_dev 2023. 9. 25. 21:31

프로세스 동기화

동시다발적으로 실행되는 프로세스들은 서로 협력하며 영향을 주고 받는다

이 과정에서 자원의 일관성을 보장해야 한다.

즉 프로세스들의 동기화를 고려해야 한다.

 

동기화 : 공동의 목적을 위해 동시에 수행되는 프로세스

ex) 워드 프로세서 프로그램은 맞춤법 검사 프로세스와 입력 내용을 화면에 출력하는 프로세스 등이 있는데 이걸 막 실행하면 안되고 올바른 수행을 위해 동기화 되어야 한다.


(프로세스) 동기화란?

:프로세스들의 수행 시기를 맞추는 것

 

1.실행 순서 제어 : 프로세스를 올바른 순서대로 실행

2.상호 배제 : 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기

 

이때 실행의 문맥을 갖는 모든 대상은 동기화 대상이기 때문에 스레드도 동기화 대상이다.


실행 순서 제어를 위한 동기화 : Reader Writer Problem

Writer : Book.txt 파일에 값을 저장하는 프로세스

Reader : Book.txt 파일에 저장된 값을 읽어들이는 프로세스

 

Reader와 Writer 프로세스는 무작정 아무렇게나 실행되어선 안된다!

실행의 순서가 있기 때문

Reader 프로세스는 ‘Book.txt 안에 값이 존재한다’는 특정 조건이 만족되어야만 실행 가능


상호 배제를 위한 동기화 : Bank account problem

공유가 불가능한 자원의 동시 사용을 피하기 위한 동기화

 

ex) 한번에 하나의 프로세스만 이용 가능한 자원 사용할 때

 

현재 계좌에 잔액 : 10만원

 

프로세스 A

  1. 계좌의 잔액 읽어 들임
  2. 읽어 들인 잔액에 2만원을 더함
  3. 더한 값을 저장

프로세스 B

  1. 계좌의 잔액을 읽어 들임
  2. 읽어 들인 잔액에 5만원을 더함
  3. 더한 값을 저장

이때 프로세스 A, 프로세스 B를 동시에 실행하면? 17만원이 계좌에 남을까?

→ ㄴㄴㄴㄴㄴ 아님!

동시에 접근해서는 안되는 자원에 동시에 접근해서 제대로 된 값이 저장이 안됨

 

 

ex) 올바르지 않은 예시

프로세스 A가 잔액 읽어 들임(10만원)

읽어 들인 값에서 2만원을 더함(10만원)

문맥 교환

 

프로세스 B가 잔액을 읽어 들임 (10만원)

읽어 들인 값에서 5만원을 더함 (10만원)

문맥 교환

 

프로세스 A 더한 값 저장 (12만원)

프로세스 B 더한 값 저장 (15만원)

최종 잔액 = 15만원?

 

이런식으로 동시에 접근해서는 안되는 잔액에 두 프로세스가 동시에 접근하다보니

컨텍스트 스위칭 일어나면서 혼선이 생김

결과 값이 올바르게 되려면 밑에처럼 바뀌어야 됨!

 

 

ex) 올바른 예시

프로세스 A가 잔액 읽어 들임(10만원)

읽어 들인 값에서 2만원을 더함(10만원)

문맥교환

 

프로세스 A 더한 값 저장 (12만원)

프로세스 B가 잔액을 읽어 들임 (10만원)

읽어 들인 값에서 5만원을 더함 (10만원)

문맥 교환

 

프로세스 B 더한 값 저장 (15만원)


마지막 동기화 예제

Producer & Consumer Problem

 

물건을 계속해서 생산하는 생산자(producer, 프로세스 혹은 스레드)

물건을 계속해서 소비하는 소비자(consumer, 프로세스 혹은 스레드)

‘총합’ 변수 공유

총합 = 10

생산자 () {
    버퍼에 데이터 삽입
    '총합' 변수 1 증가
}

소비자 () {
    버퍼에서 데이터 빼내기
    '총합' 변수 1 감소
}

이 상태에서 생산자를 100,000번 그리고 소비자를 100,000번 실행하면 ‘총합’은?

때로는 0과 다른 값이 되거나, 오류가 발생하기도 한다!!

 

why? 동기화가 되지 않았기 때문에

동시에 접근해서는 안되는 자원(총합)에 동시에 접근해서 발생하는 문제

 

 

공유 자원 : 여러 프로세스 혹은 스레드가 공유하는 자원

  • 전역 변수, 파일, 입출력장치, 보조기억장치 …..

임계 구역 : 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역

  • 앞선 예시의 ‘총합’ 변수, ‘잔액’ 변수…

만약 임계 구역에 동시에 접근하면 자원의 일관성이 깨질 수 있다.

이를 레이스 컨디선(race condition)이라고 함


우리가 쓰는 고급언어에서는 밑에처럼 간단히 1줄로 기능 하나를 구현하지만

컴퓨터가 읽어 들이는 기계언어로 바뀌면 이게 1줄이 아니라 여러 줄로 변환된다.

그래서 1줄짜리 코드를 실행하는데 왜 동기화 문제가 발생하지라고 생각하면 안됨!!!

기계어로 변환되고 실행되는 과정에서 컨텍스트 스위칭이 잘못되면서 값이 이상하게 되는 것!!!

ex) 고급 언어

총합 ++; // 총합 변수를 1 증가 가시키는 코드
총합 --; // 총합 변수를 1 감소 시키는 코드

ex) 저급 언어

// 총합 증가 시키는 코드

r1 = 총합; // 총합 변수를 레지스터에 저장
r1 = r1 + 1; // 레지스터 값 1 증가
총합 = r1; // 레지스터 값을 총합 변수에 저장

// 총합 감소 시키는 코드

r2 = 총합; // 총합 변수를 레지스터에 저장
r2 = r2 - 1; // 레지스터 값 1 감소
총합 = r2 // 레지스터 값을 총합 변수에 저장

예를 들어 1을 증가시키는 코드와 감소 시키는 코드를 실행하다보면 밑에처럼 될 수 있음

r1 = 총합  // 총합/r1/r2(10,10, )
r1 = r1 + 1 // 10,11
문맥 교환

r2 = 총합 // (10, 11, 10)
r2 = r2 - 1 // (10, 11, 9)
문맥교환

총합 = r1 // (11,11,9)
문맥교환

총합 = r2 // (9,11,9)

운영체제가 임계 구역 문제를 해결하는 세 가지 원칙

(상호 배제를 위한 동기화를 위한 세 가지 원칙)

  1. 상호 배제 (mutual exclusion) : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 들어올 수 없다.
  2. 진행 (progress) : 임계 구역에 어떤 프로세스도 진입하지 않았다면 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
  3. 유한 대기 (bounded waiting) : 한 프로세스가 임계 구역에 진입하고 싶다면 언젠가는 임계 구역에 들어올 수 있어야 한다(임계 구역에 들어오기 위해 무한정 대기해서는 안된다.)

동기화 기법

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

뮤텍스 락

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

ex)

탈의실을 임계 구역, 프로세스를 사람이라고 가정 한다면

탈의실이 잠겨있거나 자물쇠가 걸려있다면 사람이 있겠구나 하고 동시에 안들어가겠지?

 

이걸 코드로 구현한 게 뮤텍스 락!

뮤텍스 락의 단순한 형태는 전역 변수 1개와 함수 2개로 일단 구현 가능

 

ex)

자물쇠 역할 : 프로세스들이 공유하는 전역 변수, lock 변수

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

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

acquire() { 
    while (lock == true) // 만약 임계 구역이 잠겨 있다면
        ; // ; 하나 해놓은 거는 c언어에서 무한 반복 의미, 임계 구역이 잠겨 있는지를 반복적으로 확인
            // lock가 true인지 false인지 계속 확인하다가 
            // false면 나도 이제 들어가서 작업할 수 있으니 들어가고 잠그는 느낌!
    lock = true; // 만약 임계 구역이 잠겨 있지 않다면 임계 구역 잠금
}

release() {
    lock = false; //임계 구역 작업이 끝났으니 잠금 해제
}

--------------------------------------------------------------------------------

acquire(); // 자물쇠 잠겨 있는지 확인, 잠겨 있지 않다면 잠그고 들어가기

임계구역 // 임계 구역에서의 작업 진행

release(); //자물쇠 반환

acquire 함수

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

release 함수

  • 임계 구역에서의 작업이 끝나고 호출
  • 현재 잠긴 임계 구역을 열기 (lock을 false로 바꾸기)

바쁜 대기 (busy waiting)

: 밑에 코드처럼 임계 구역 열려있는지 아닌지 무한히 반복하는 걸 의미 → 물론 좋지는 않음

while (lock == true)
    ;

 


세마포

: 세마포의 종류도 이진 세마포와 카운팅 세마포가 있는데 일단 카운팅 세마포를 다루겠음!!

  • 뮤텍스보다 좀 더 일반화된 동기화 도구 (공유 자원이 여러개여도 적용 가능)
  • 공유 자원이 여러 개 있는 경우에도 적용 가능

세마포는 원래 철도 신호기를 지칭하는 단어

  • 임계 구역 앞에서 멈춤 신호를 받으면 잠시 기다리기
  • 임계 구역 앞에서 가도 좋다는 신호를 받으면 임계 구역 진입

세마포도 전역변수 1개와 함수 2개로 간단한 형태는 구현 가능

 

ex)

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

signal() {
    S ++; // 임계 구역에서의 작업을 마친 뒤 S를 1 증가 시킨다.
}

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

  1. 프로세스 P1 wait 호출. S는 현재 2이므로 S를 1 감소 시키고 임계 구역 진입
  1. 프로세스 P2 wait 호출. S는 현재 1이므로 S를 1 감소 시키고 임계 구역 진입
  2. 프로세스 P3 wait 호출. S는 현재 0이므로 무한히 반복하며 S 확인
  3. 프로세스 P1 임계 구역 작업 종료, signal() 호출. S를 1 증가
  4. 프로세스 P3 S가 1이 됨을 확인. S는 현재 1이므로 S를 1 감소 시키고 임계 구역 진입

여기서도 Busy Waiting 부분이 있음

: 마치 탈의실 문이 열렸는지 / 닫혔는지 반복적으로 확인하는 것과 같다

→ CPU 사이클 낭비!!

wait() {
    while ( S <= 0) // 만일 임계 구역에 진입할 수 있는 프로세스 개수가 0 이하라면
    ; // 사용할 수 있는 자원이 있는지 반복적으로 확인하고,
    S --; // 임계 구역에 진입할 수 있는 프로세스 개수가 하나 이상이면 S를 1 감소 시키고 임계 구역 진입

해결 방법 (프로세스의 상태를 바꿈으로써, cpu의 불필요한 낭비 방지)

  • 사용할 수 있는 자원이 없을 경우 대기 상태로 만듦
    (해당 프로세스의 PCB를 대기 큐에 삽입)
  • 사용할 수 있는 자원이 생겼을 경우 대기 큐의 프로세스를 준비 상태로 만듦
    (해당 프로세스의 PCB를 대기 큐에서 꺼내 준비 큐에 삽입)

해결 방법 적용 후 세마포 코드

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

signal() {
    S++;
    if ( S <= 0 ) {
        remove a process p from Queue; // 대기 큐에 있는 프로세스 P를 제거한다.
        wakeup(p); // 프로세스 p를 대기 상태에서 준비 상태로 만든다.
    }
}

이제 아까처럼

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

  1. 프로세스 P1 wait 호출. S는 현재 2이므로 S를 1 감소 시키고 임계 구역 진입
  1. 프로세스 P2 wait 호출. S는 현재 1이므로 S를 1 감소 시키고 임계 구역 진입
  2. 프로세스 P3 wait 호출. S를 1 감소 시키면 S는 -1이므로, 본인의 PCB를 대기 큐에 넣고 대기 상태로 전환
  3. 프로세스 P1 임계 구역 작업 종료, signal() 호출. S를 1 증가하면 0이므로 대기 상태였던 P3를 대기 큐에서 꺼내 준비 큐로 옮겨줌
  4. 깨어난 프로세스 P3 임계 구역 진입
  5. 프로세스 P2 임게 구역 작업 종료, signal() 호출. S가 1 증가하면 1
  6. 프로세스 P3 임계 구역 작업 종료, signal() 호출. S가 1 증가하면 2

앞에 부분이 상호 배제 부분을 다룬 동기화라면

실행 순서를 다룬 동기화도 있음

  • 세마포의 변수 S를 0으로 두고,
  • 먼저 실행할 프로세스 임계구역 뒤에 signal 함수,
  • 다음에 실행할 프로세스 임계구역 앞에 wait 함수를 붙이면 됨

단점 : 임계구역 앞뒤로 wait(), signal()을 호출해야 하니까 실수 발생 가능!!!!

그리고 순서 헷갈리거나 중복해서 사용하거나 세마포를 누락한 경우는 디버깅하기도 어려워서 사용자 입장에서 사용하기 어려움

→ 그래서 등장한게 모니터!

 

 


 

모니터

:상호 배제를 위한 동기화와 실행 순서 제어를 위한 동기화 방식 모두 제공해주는 동기화 도구

프로그래밍 언어, 혹은 프레임워크 차원에서 제공하는 기법

ex) Java의 경우 synchronized 

 

모니터는 공유 자원에 접근하기 위한 인터페이스만을 제공한다.

 

 

 

1. 모니터에서 먼저 상호 배제를 위한 동기화 부터 알아보자면

 

공유자원과 그 공유자원에 접근하기 위한 통로를 묶어서 관리

 

그 공유 자원에 접근하고자 하는 스레드와 프로세스는 반드시 특정 인터페이스를 통해 접근해야 함

인터페이스를 위한 큐가 있기 때문에 공유자원에는 한번에 하나의 프로세스만 들어가겠지

  • 인터페이스를 위한 큐
  • 공유자원에 접근하고자 하는 프로세스를 (인터페이스를 위한) 큐에 삽입
  • 큐에 삽입된 순서대로(한번에 하나의 프로세스만) 공유 자원 이용

 

 

 

 

2. 모니터에서 실행 순서 제어를 위한 동기화

  • 조건 변수(condition variable) 이용 → 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수, wait()과 signal() 호출할 수 있는…
  • 조건변수.wait() : 대기 상태로 변경, 조건 변수에 대한 큐에 삽입

Ex) 어떤 프로세스 내에서 x라는 조건 변수에 대해 wait() 호출하면 조건 변수 x에 대해서 큐에 삽입이 됨

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

ex) 프로세스 a,b가 있고 프로세스 b가 무조건 먼저 실행돼야 한다면, a는 임의의 조건 변수 x에 대해 x.wait() 호출하면 조건 변수에 대한 큐로 들어가서 대기상태로 접어들 것이고 그러면 그 다음에 있던 b가 실행 될 것임. 이때 b가 실행이 끝나고 x.siganl() 호출하면 이 조건 변수에 대해 대기상태에 접어들었던 a가 다시 모니터안으로 들어가서 실행 가능해짐

 

여기서 모니터 안에는 하나의 프로세스만이 있을 수 있다. 밑의 2가지 방식이 있을 수 있음

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

정리: 실행 순서 제어를 위한 동기화에서의 모니터는

  1. 특정 프로세스가 아직 실행될 조건이 되지 않았을 때에는 wait을 통해 실행을 중단
  2. 특정 프로세스가 실행될 조건이 충족되었을 때에는 signal 통해 실행 재개

 

 

'혼자 공부하는 컴퓨터구조+운영체제' 카테고리의 다른 글

파일과 디렉토리  (0) 2023.10.01
교착 상태  (0) 2023.09.26
cpu 스케줄링  (0) 2023.09.24
프로세스와 스레드  (0) 2023.09.21
운영체제  (0) 2023.09.19
Comments