개발자 도전기
[STUDY] 프로세스 동기화 본문
✅ 프로세스 동기화란?
다중 프로세스 환경에서 프로세스 간의 상호작용을 조정하는 메커니즘으로
여러 프로세스가 공유 자원에 접근할 때 일관성과 안정성을 보장하고 프로세스 간의 순서와 시간을 조절한다.
👉 동기화의 종류
○ 실행 순서 제어
: 프로세스를 올바른 순서대로 실행하는 것
ex ) reader writer problem
리더와 라이터 간의 충돌이 발생해서는 안 된다.
○ 상호 배제
: 동시에 접근해서는 안 되는 자원에 하나의 프로세스만 접근하게 하기
ex ) Bank account problem
A 카드사와 B 카드사가 동시에 계좌에 접근하려고 할 때 문제가 생길 수 있다.
✅ 공유 자원과 임계 구역
○ 공유자원
: 여러 프로세스가 공유하는 자원 (전역 변수, 입출력장치, 보조기억장치)
○ 임계 구역
: 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역
- 임계 구역에 진입하고자 하면 진입한 프로세스 이외에는 대기해야 한다
- 임계 구역에 동시에 접근하면 자원의 일관성이 깨질 수 있다
- 이를 레이스 컨디션(race condition)이라고 한다
✅ 상호 배제 동기화를 위한 세 가지 원칙
1. 상호 배제(Mutual Exclusion)
: 둘 이상의 프로세스가 동시에 공유 자원을 사용하지 못하도록 제어하는 것. 한 프로세스가 공유 자원을 사용하는 동안 다른 프로세스는 대기해야 한다
2. 진행(Progress)
: 임계 영역에 들어가고자 하는 프로세스나 스레드가 없을 때, 다른 프로세스나 스레드가 임계 영역에 진입할 수 있어야 함. 데드락 상황 방지
3. 유한 대기(Bounded Waiting)
: 임계 영역에 들어가고자 하는 프로세스나 스레드가 대기하는 시간이 유한해야 함
✅ 동기화 기법
👉 뮤텍스 락
: 상호 배제를 위한 동기화 도구
* 자물쇠 역할 : 프로세스들이 공유하는 전역 변수 lock
* 임계 구역을 잠그는 역할 : acquire 함수
* 임계 구역의 잠금을 해제하는 역할 : release 함수
public class Mutex {
static Boolean lock = false;
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<>();
Thread t1 = new Thread(() -> {
acquire();
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
release();
});
Thread t2 = new Thread(() -> {
acquire();
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
release();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(list); // []
}
static void acquire() {
while (lock == true) // 만약 임계 구역이 잠겨 있다면
; // 임계 구역이 잠겨 있는지를 반복적으로 확인
lock = true; // 만약 임계 구역이 잠겨 있지 않다면 임계 구역을 잠금
}
static void release() {
lock = false; // 임계 구역 작업이 끝났으니 잠금 해제
}
}
🚩 busy waiting(바쁜 대기) : 임계 구역이 잠겨있는지 반복적으로 확인하는 것
👉 세마포
: 좀 더 일반화된 방식의 동기화 도구, 공유 자원이 여러 개 있는 경우에도 적용 가능
🚩 세마포 : 철도 신호기에서 유래
* 임계 구역에 진입할 수 있는 프로세스의 개수(사용 가능한 공유 자원의 개수)를 나타내는 전역 변수 S
* 임계구역에 들어가도 좋은지, 기다려야 할지를 알려주는 wait 함수
* 임계구역 앞에서 기다리는 프로세스에 '이제 가도 좋다'고 신호를 주는 signal 함수
public class Semaphore {
static int s = 2;
public static void main(String[] args) throws InterruptedException {
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<String> list3 = new ArrayList<>();
Thread t1 = new Thread(() -> {
s_wait();
for (int i = 0; i < 30000; i++) {
list1.add("a");
list1.remove("a");
}
signal();
});
Thread t2 = new Thread(() -> {
s_wait();
for (int i = 0; i < 30000; i++) {
list2.add("b");
list2.remove("b");
}
signal();
});
Thread t3 = new Thread(() -> {
s_wait();
for (int i = 0; i < 30000; i++) {
list3.add("c");
list3.remove("c");
}
signal();
});
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println(list1); // []
System.out.println(list2); // []
System.out.println(list3); // []
}
static void s_wait() {
while (s <= 0) // busy waiting
;
s--; // 사용 가능한 공유 자원 수 -
}
static void signal() {
s++; // 사용 가능한 공유 자원 수 +
}
}
😂 세마포의 단점
- 매번 임계구역 앞뒤로 함수를 호출해야 함, 누락의 경우 오류
👉 모니터
: 개발자가 다루기에 편한 동기화 도구
상호 배제를 위한 동기화
- 인터페이스를 위한 큐
- 공유 자원에 접근하고자 하는 프로세스를 큐에 삽입
- 큐에 삽입된 순서대로 (한 번에 하나의 프로세스만) 공유 자원 이용
public class Monitor {
static Boolean lock = false;
public static void main(String[] args) throws InterruptedException {
Queue<Thread> queue = new LinkedList<>(); // 스레드를 넣을 큐 생성
List<String> list = new ArrayList<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
});
Thread t3 = new Thread(() -> {
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
});
// 스레드를 큐에 담기
queue.offer(t1);
queue.offer(t2);
queue.offer(t3);
// 큐에 담은 순서대로 스레드 실행
while (!queue.isEmpty()) {
Thread t = queue.poll();
t.start();
t.join();
}
System.out.println(list);
}
}
'개발공부 > CS스터디' 카테고리의 다른 글
[STUDY] 페이징 (0) | 2024.04.16 |
---|---|
[STUDY] 가상메모리와 메모리 할당 (0) | 2024.04.12 |
[Study] 시간복잡도, Big-O (0) | 2024.03.27 |
[STUDY] 함수형 프로그래밍 (0) | 2024.03.21 |
[STUDY] 캐시 메모리와 버퍼 메모리 (0) | 2024.03.06 |