Post

프로세스 동기화

프로세스 간 통신

프로세스 간 통신의 개념

  • 프로세스 내부 데이터 통신 : 하나의 프로세스 내에 2개 이상의 스레드가 존재하는 경우 프로세스 내의 스레드는 전역 변수나 파일을 이용하여 데이터를 주고받는다.
  • 프로세스 간 데이터 통신 : 같은 컴퓨터에 있는 여러 프로세스끼리 통신하는 경우로, 공용 파일 또는 운영체제가 제공하는 파이프를 사용하여 통신한다.
  • 네트워크를 이용한 데이터 통신 : 여러 컴퓨터가 네트워크로 연결되어 있을 때 소켓을 이용한 프로세스 간 통신을 네트워킹이라고 한다.

프로세스 간 통신의 분류

  • 통신 방향에 따른 분류

    • 양방향 통신 : 데이터를 동시에 양쪽 방향으로 전송할 수 있는 구조로, 일반적인 통신은 모두 양방향 통신이다. ex) 소켓
    • 반양방향 통신 : 데이터를 양쪽 방향으로 전송할 수 있지만 동시 전송은 불가능하고 특정 시점에 한쪽 방향으로만 전송할 수 있는 구조. ex) 무전기
    • 단방향 통신 : 한쪽 방향으로만 데이터를 전송할 수 있는 구조. ex) 전역 변수, 파일, 파이프
  • 통신 구현 방식에 따른 분류

    • 대기가 있는 통신(동기화 통신) : 동기화를 지원하는 통신 방식. 데이터를 받는 쪽은 데이터가 도착할 때까지 자동으로 대기 상태에 머물러 있다. ex) 파이프, 소켓
    • 대기가 없는 통신(비동기화 통신) : 동기화를 지원하지 않는 통신 방식. 데이터를 받는 쪽은 바쁜 대기를 사용하여 대기가 도착했는지 여부를 직접 확인한다. ex) 전역 변수, 파일

전역 변수를 사용하는 통신 방식의 단점은 언제 데이터를 보낼지 데이터를 받는 쪽에서 모른다는 것이다. 우편함에 편지가 있는지 수시로 열어보는 것처럼 상태 변화를 살펴보기 위해 반복문을 무한 실행하며 기다리는 것을 바쁜 대기라고 한다.

프로세스 간 통신의 종류

  1. 전역 변수를 이용한 통신
  • 공동으로 관리하는 메모리를 사용하여 데이터를 주고받는다.
  • 직접적으로 관련이 있는 프로세스간에 사용한다.
  • 부모-자식 프로세스간에 메모리를 공유한다.
  1. 파일을 이용한 통신
  • 파일 열기
    • open() 함수를 이용하여 사용하고자 하는 파일이 있는지, 있다면 쓰기 권한이 있는지도 확인한다.
    • open() 함수는 해당 파일에 접근할 수 있는 권한인 fd(파일 기술자)를 반환한다.
    • 어떤 파일에 쓰기나 읽기 연산을 하려면 먼저 fd를 얻어야 하며 작업이 다 끝나면 fd를 돌려주어야 한다.
  • 쓰기 또는 읽기
    • write(), read()
    • 쓰기나 읽기 연산의 맨 앞에는 fn을 사용한다.
  • 닫기
    • close() 함수를 이용하여 파일을 닫는다.
1
2
3
4
5
6
7
8
9
10
11
12
fd = open('com.txt', O_RDWR);
// com.txt 파일을 읽기와 쓰기를 할 수 있는 형태로 준비한다.
// O_RDWR은 읽기와 쓰기 작업을 하겠다는 것이며, 만약 읽기 전용으로 파일을 열고자 할 때는 O_RDONLY라고 하면 된다.

write(fd, 'Test', 5);
// fd, 즉 com.txt 파일에 "Test"라는 문자열을 쓰라는 뜻이다.
// 문자열의 끝을 알리는 특수 부호인 null을 포함한 Test의 크기가 5B이기 때문에 마지막에 5라고 명시했다.

read(fd, buf, 5);
// fd, 즉 com.txt 파일에서 5B를 읽어 변수 buf에 저장하라는 뜻이다.

close(fd);
  1. 파이프를 이용한 통신
  • 파이프는 운영체제가 제공하는 동기화 통신 방식으로, 파일 입출력과 같이 open() 함수로 기술자를 얻고 작업을 한 후 close() 함수로 마무리한다.

  • 파이프를 이용한 통신은 단방향 통신이며 양방향 통신을 하려면 파이프 2개를 사용해야 한다.

  • 만약 프로세스 B가 파이프 1에 대해 읽기 연산을 수행했는데 프로세스 A가 파이프 1에 아직 쓰기 연산을 하지 않았다면 프로세스 B는 대기 상태가 된다. 이러한 대기 상태는 프로세스 A가 파이프 1에 데이터를 쓰는 순간 자동으로 풀려 동기화가 이루어진다.

  • 이름 없는 파이프 : 부모와 자식 프로세스 혹은 같은 부모를 가진 자식 프로세스와 같이 서로 관련 있는 프로세스 간 통신에 사용된다.

  • 이름 있는 파이프 : FIFO라 불리는 특수 파일을 이용하며 서로 관련 없는 프로세스 간 통신에 사용된다.

  1. 소켓을 이용한 통신
  • 여러 컴퓨터에 있는 프로세스 간 통신은 네트워킹이라고 한다.
  • 네트워킹 상황에서의 통신은 원격 프로시저 호출이나 소켓을 이용한다.
  • 원격 프로시저 호출은 다른 컴퓨터에 있는 함수를 호출하는 것이다. ex) 자바
  • 일반적으로 원격 프로시저 호출은 소켓을 이용하여 구현한다.
  • 통신하고자 하는 프로세스는 소켓에 쓰기 연산을 하면 데이터가 전송되고, 읽기 연산을 하면 데이터를 받게 된다.
  • 양방향 통신을 하기 위해 파이프는 2개를 사용했지만 소켓은 하나만 사용해도 양방향 통신이 가능하다.
  • 네트워크 프로그래밍을 흔히 소켓 프로그래밍이라고 부르는 이유는 네트워킹의 기본이 소켓이기 떄문이다.

공유 자원과 임계구역

공유 자원의 접근

  • 공유 자원 : 여러 프로세스가 공동으로 이용하는 변수, 메모리, 파일 등을 말한다.
  • 경쟁 조건 : 공유 자원을 병행적으로 읽거나 쓰는 상황

임계구역

  • 임계구역 : 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역
  • 어떤 프로세스가 임계구역에 들어가면 다른 프로세스는 기다려야 하며 임계구역의 프로세스가 나와야 들어갈 수 있다.

생산자-소비자 문제

  • 생산자-소비자 문제 : 임계구역과 관련된 전통적인 문제로 생산자 프로세스와 소비자 프로세스가 서로 독립적으로 작업을 한다.

임계구역 해결 조건

  • 상호 배제 : 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없다. 임계구역 내에는 한 번에 하나의 프로세스만 있어야 한다.
  • 한정 대기 : 어떤 프로세스도 무한 대기하지 않아야 한다. 즉 특정 프로세스가 임계구역에 진입하지 못하면 안 된다.
  • 진행의 융통성 : 한 프로세스가 다른 프로세스의 진행을 방해해서는 안 된다.

임계구역 해결 방법

임계구역 문제를 해결하는 가장 단순한 방법은 잠금(lock)을 이용하는 것이다.

기본 코드 소개

  • boolean 변수는 true와 false 값만 가질 수 있으므로 잠금을 표현하기에 적합하다.
  • 프로세스가 공유하는 변수는 ‘공유 변수’, 임계구역으로 보호되어야 할 부분은 ‘임계구역’이라고 표시할 것이다.

임계구역 해결 조건을 고려한 코드 설계

  1. 상호 배제 문제

프로세스 P1과 P2는 임계구역에 진입하기 전에 while(lock==true) 문을 실행하여 잠금이 해제될 때까지 무한 루프를 돌면서 기다린다.

1
2
3
4
5
while (lock == true);
// 타임아웃
lock = true;
// 동시 진입
lock = false;
  • P1이 lock=true; 문을 실행하기 전에 타임아웃으로 준비 상태로 옮겨지면 P2가 임계구역에 진입하게 되어 결국 둘 다 임계구역에 진입하게 된다.
  • 또 다른 문제는 잠금이 풀리기를 기다리려면 바쁜 대기를 한다는 것이다.
  1. 한정 대기 문제

아래 코드는 상호 배제를 보장하지 못하는 문제를 보완하여 전역 변수로 lock1과 lock2를 사용한다.

1
2
3
4
while (lock1 == true);
lock1 = true;

lock = false;
1
2
3
4
while (lock2 == true);
lock2 = true;

lock = false;
  • P1은 lock1=true; 문을 실행한 후 타임아웃 되어 실행 상태로 바뀐다. P2도 lock2=true; 문을 실행한 후 타임아웃 되어 실행 상태로 바뀐다.
  • 두 프로세스는 각각 lock=true; 문을 실행했기 때문에 while(lock==true); 문에서 무한 루프에 빠진다.
  • 이는 한정 대기 조건을 보장하지 못하는 상황으로 교착 상태(deadlock)라고 한다.
  • 또한 프로세스가 늘어나면 검사해야 하는 lock의 개수도 늘어나 비효율적이다.
  1. 진행의 융통성 문제

아래 코드는 lock 값이 1이면 P1이 임계구역을 사용하고, 2이면 P2가 임계구역을 사용한다.

1
2
// 공유 변수
int lock=1;
1
2
3
while (lock == 2);

lock = 2;
1
2
3
while (lock == 1);

lock = 1;
  • 잠금을 확인하는 문장이 하나이므로 상호 배제와 한정 대기를 보장한다.
  • P1은 P2가 임계구역에 진입했다 나와야 다시 진입할 수 있으므로 한 프로세스가 두 번 연달아 임계구역에 진입할 수 없다.
  • 이렇게 프로세스의 진행이 다른 프로세스로 인해 방해받는 현상을 경직된 동기화라고 하며, 진행의 융통성 조건을 보장하지 못한다.
  1. 하드웨어적인 해결 방법

하드웨어적으로 while(lock==true); 문과 lock=true; 두 명령어를 동시에 실행하면 해결할 수 있다.

1
2
3
while(testandset(&lock)==true);

lock=false;
  • 임계구역을 하드웨어적으로 해결하면 편리하지만 바쁜 대기를 사용하지 때문에 자원 낭비가 있다.

피터슨 알고리즘과 데커 알고리즘

피터슨 알고리즘

1
2
3
4
// 공유 변수
boolean lock1=false;
boolean lock2=false;
int turn=1;
1
2
3
4
5
lock1 = true;
turn = 2;
while (lock2 == true && turn == 2);

lock1 = false;
1
2
3
4
5
lock2 = true;
turn = 1;
while (lock2 == true && turn == 1);

lock2 = false;
  • 두 프로세스가 동시에 lock을 설정했더라도 turn을 사용하여 한 프로세스만 임계구역에 진입할 수 있다.
  • 임계구역 해결의 세 가지 조건을 만족하지만 2개의 프로세스만 사용 가능하다는 한계가 있다.
  • 여러 프로세스가 하나의 임계구역을 사용하려면 공유 변수를 추가해야 한다.

세마포어

1
2
3
4
5
Semaphore(n);

P();

V();
  • Semaphore(n) : 전역 변수 RS를 n(자원의 수)으로 초기화한다.
  • P() : RS가 0보다 크면 1만큼 감소시키고 임계구역에 진입하고 0보다 작으면 0보다 커질 때까지 기다린다.
  • V() : RS 값을 1씩 증가시키고 세마포어에서 기다리는 프로세스에게 임계구역에 진입해도 좋다는 wake_up 신호를 보낸다.
  • 세마포어의 가장 큰 문제는 사용자의 잘못된 사용으로 임계구역이 보호받지 못할 수 있다는 것이다.

모니터

모니터는 공유 자원을 내부적으로 숨기고 공유 자원에 접근하기 위한 인터페이스만 제공하며 이를 시스템 호출이라고 한다.

  • 임계구역으로 지정된 변수나 자원에 접근하고자 하는 프로세스는 직접 P()나 V()를 사용하지 않고 모니터에 작업 요청을 한다.
  • 모니터는 요청받은 작업을 모니터 큐에 저장한 후 순서대로 처리하고 그 결과만 해당 프로세스에 알려준다.

  • wait() : 모니터 큐에서 자신의 차례가 올 떄까지 기다린다. 세마포어의 P()에 해당한다.
  • signal() : 모니터 큐에서 기다리는 다음 프로세스에 순서를 넘겨준다. 세마포어의 V()에 해당한다.
This post is licensed under CC BY 4.0 by the author.