개발자 도전기

[STUDY] 프로세스 동기화 본문

개발공부/코딩용어

[STUDY] 프로세스 동기화

jnnjnn 2024. 4. 2. 23:50

 

✅ 프로세스 동기화란?

 

다중 프로세스 환경에서 프로세스 간의 상호작용을 조정하는 메커니즘으로

여러 프로세스가 공유 자원에 접근할 때 일관성과 안정성을 보장하고 프로세스 간의 순서와 시간을 조절한다.

 

👉 동기화의 종류

 

실행 순서 제어 : 프로세스를 올바른 순서대로 실행하는 것

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);
    }
}

 

'개발공부 > 코딩용어' 카테고리의 다른 글

[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