본문 바로가기
CS/운영체제

운영체제의 병행 프로세스, 병행성 문제, 세마포어, 상호배제와 동기화 해결 세마포어 자바 구현 코드

by Renechoi 2023. 6. 15.

 

1. 병행 프로세스 

 

병행성(Concurrency) 

=> 여러 개의 프로세스 또는 쓰레드가 동시 수행되는 시스템의 특성 

 

병행 프로세스: 동시 수행되는 여러 개의 프로세스 또는 쓰레드 

 

 

1개의 CPU: 인터리빙 형식 => CPU 입장에서는 순차적으로 처리하는 것 

 

여러 개의 CPU: 병렬 처리 형식 

 

멀티 프로세서 시스템에서의 메모리 구조에 따라 

- 강결합 시스템 (공유 메모리 구조) 

- 약결합 시스템 (분산 메모리 구조)

 

약결합 시스템에서는 CPU 마다 별도 메모리를 갖는 것. 

 

 

 

독립 프로세스

- 수행중인 다른 프로세스에 영향을 주지도 받지도 않음

- 데이터 및 상태를 다른 프로세스와 공유하지 않음 

- 프로세스의 실행

  -> 결정적: 실행 결과는 입력에 의해서만 결정됨

  -> 재생가능 : 같은 입력에 대해 항상 동일한 실행 결과 

 

 

 

협력 프로세스 <-> 독립 

- 수행중인 다른 프로세스와 영향을 주고 받음

- 데이터 및 상태를 다른 프로세스와 공유

- 프로세스의 실행 

  -> 비결정적: 실행 결과는 실행 순서에 좌우됨 

  -> 재생불가능: 같은 입력에 대해 항상 동일한 실행결과를 보장하지 못함 

 

 

 

2. 병행성 문제 

 

 

협력 프로세스인 경우 발생 가능한 문제

- 상호 배제

- 동기화

- 통신 

 

 

상호배제

- 2개 이상의 프로세스가 동시에 임계영역을 수행하지 못하돌고 하는 것

- 임계영역: 2개 이상의 프로세스가 동시에 사용하면 안되는 공유자원을 액세스 하는 프로그램 코드 영역 

e.g. 은행의 공용으로 쓰이는 잔고는 공유 자원 

 

 

동기화 

- 2개 이상의 프로세스에 대한 처리 순서를 결정하는 것

- 프로세스 동기화 

- 상호배제: 임계영역에 대한 동기화 문제 

e.g. 출금이 먼저 되고 입금이 되어야 하는데 순서가 틀리면 잘못된 결과를 낳을 수 있음 

 

 

통신

- 프로세스들이 데이터를 공유하기 위해 반드시 필요

- 프로세스 간 통신 (IPC) 

 

통신 방법 

- 하나의 변수 사용 (공유 변수)

- 메시지를 주고 받음 

 

 

3. 세마포어 

- 상호배제와 동기화 문제를 해결하기 위한 도구

- Dijkstra가 제안 

 

-정수형 공용변수

=> 저장값: 사용가능한 자원의 수 또는 잠김이나 풀림의 상태를 의미 

 

- 상황에 맞춰 0 이상인 정수로 초기화 

 

- 두 기본연산 p와 v에 의해서만 사용됨

  -> 기본연산: 인터럽트 되지 않고 하나의 단위로 처리됨 

 

 

연산 p : 검사, 감소시키려는 시도 

연산 v: 증가 

 

 

 

세마포어마다 대기 큐 필요 

 

 

상호배제 해결하기 

 

상호배제를 위한 일반적인 요구 사항 

- 한 프로세스가 임계영역 수행 중 다른 프로세스는 임계영역에 진입해서는 안 됨 

- 임계영역 수행중이던 프로세스가 임계영역을 벗어나면 누군가 하나는 임계영역을 새로이 수행할 수 있어야 함

- 임계영역 진입 못하고 대기하는 프로세스: 적절한 시간 내에 임계영역 수행을 시작해야 함 

 

 

상호 배제를 위한 임계영역 주변의 코드 

 

-> 임계 영역에 대한 수행 가능 여부를 체크한다. 

-> 다른 프로세스가 임계 영역 수행을 시작할 수 있도록 한다. 

 

 

세마포어를 이용하기 

 

 

 

세마포어 mutex의 초깃값은 1로 설정

진입영역: p 

해제영역 v

대기큐는 FIFO로 동작 

 

 

먼저 A가 들어온다고 할 때 

-> p에서 통과 하여 임계영역 진입 

-> 이후 

-> 대기 큐 상황에 따라  

-> C B => 

-> B, C 대기 

-> A가 끝나면 v 체크 하여 자원 해제 

-> B 깨워서 수행 

 

 

이를 자바로 구현한 두 가지 코드 예제 

 

첫 번째 예제 : Java.util.concurrent의 Semaphore객체를 이용 

 

package os.semaphore.basic.mutualexclusion.ex1;

import java.util.concurrent.Semaphore;

class SharedResource {
    private Semaphore semaphore;

    public SharedResource() {
        // 세마포어의 초기값을 1로 설정하여 상호 배제를 구현
        semaphore = new Semaphore(1);
    }

    public void accessResource(int processId) {
        try {
            System.out.println("프로세스 " + processId + "가 공유 자원에 접근합니다.");

            // 세마포어를 획득하고 공유 자원에 접근
            semaphore.acquire();

            // 공유 자원에 대한 작업 수행
            System.out.println("프로세스 " + processId + "가 공유 자원을 사용 중입니다.");
            Thread.sleep(2000); // 임의의 작업 시간

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("프로세스 " + processId + "가 공유 자원 사용을 완료하였습니다.");

            // 세마포어를 해제하여 다른 프로세스가 접근할 수 있도록 함
            semaphore.release();
        }
    }
}

class Process extends Thread {
    private int processId;
    private SharedResource sharedResource;

    public Process(int processId, SharedResource sharedResource) {
        this.processId = processId;
        this.sharedResource = sharedResource;
    }

    public void run() {
        sharedResource.accessResource(processId);
    }
}

public class MutualExclusion {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // 3개의 프로세스 생성
        Process process1 = new Process(1, sharedResource);
        Process process2 = new Process(2, sharedResource);
        Process process3 = new Process(3, sharedResource);

        // 각 프로세스 실행
        process1.start();
        process2.start();
        process3.start();
    }
}

 

 

두 번째 예제 코드 : 

 

다음과 같이 직접 구현한 세마포어를 이용 

 

package os.semaphore;

public class Semaphore {
    private int permits;

    public Semaphore(int initialPermits) {
        permits = initialPermits;
    }

    /**
     * 허용된 자원의 개수(permits)가 0 이하인 경우 스레드를 대기 상태로 전환 시키는 역할을 한다.
     *
     * wait(): 스레드를 일시적으로 대기 상태로 전환한다.
     * 스레드는 wait() 메서드를 호출하면 해당 객체의 모니터(lock)를 해제하고 대기 상태로 들어간다.
     * 대기 상태에서는 다른 스레드가 해당 객체의 모니터를 획득하여 작업을 수행할 수 있다.
     * 스레드가 wait() 상태에 있을 때는 notify() 또는 notifyAll() 메서드에 의해 깨어나 실행을 재개하게 된다.
     * @throws InterruptedException
     */
    public synchronized void acquire() throws InterruptedException {
        while (permits <= 0) {
            wait();
        }
        permits--;
    }

    /**
     * 자원을 해제하고 대기중인 스레드의 실행이 제기되도록 호출한다.
     *
     * notify(): 대기 중인 스레드 중 하나를 깨운다.
     * 깨어난 스레드는 wait() 메서드 이후부터 실행을 재개하며, wait() 호출 이후의 문장부터 실행된다.
     * 여러 스레드가 대기 중이라면 어떤 스레드가 깨어날지는 정해져 있지 않다.
     */
    public synchronized void release() {
        permits++;
        notify();
    }
}

 

package os.semaphore.basic.mutualexclusion.ex2;

import os.semaphore.Semaphore;

class SharedResource {
    private Semaphore mutex;

    public SharedResource() {
        // 세마포어의 초기값을 1로 설정하여 상호 배제를 구현
        mutex = new Semaphore(1);
    }

    public void accessResource(int processId) {
        try {
            System.out.println("프로세스 " + processId + "가 공유 자원에 접근합니다.");

            // 세마포어를 획득하고 공유 자원에 접근
            mutex.acquire();

            // 공유 자원에 대한 작업 수행
            System.out.println("프로세스 " + processId + "가 공유 자원을 사용 중입니다.");
            Thread.sleep(2000); // 임의의 작업 시간

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("프로세스 " + processId + "가 공유 자원 사용을 완료하였습니다.");

            // 세마포어를 해제하여 다른 프로세스가 접근할 수 있도록 함
            mutex.release();
        }
    }
}

class Process extends Thread {
    private int processId;
    private SharedResource sharedResource;

    public Process(int processId, SharedResource sharedResource) {
        this.processId = processId;
        this.sharedResource = sharedResource;
    }

    public void run() {
        sharedResource.accessResource(processId);
    }
}

public class MutualExclusion {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // 3개의 프로세스 생성
        Process process1 = new Process(1, sharedResource);
        Process process2 = new Process(2, sharedResource);
        Process process3 = new Process(3, sharedResource);

        // 각 프로세스 실행
        process1.start();
        process2.start();
        process3.start();
    }
}

 

 

 

 

동기화 문제 해결 

 

- 상황: 프로세스 A가 코드 S1을 수행한 후 프로세스 B가 코드 S2를 수행하도록 동기화 

 

세마포어 sync 초깃값은 0 

 

 

A가 먼저 실행된 경우 

-> A 프로세스가 수행되고 나서 V 체크시에 1로 업데이트 한다. 

-> 대기중인 B는 P에서 검증을 받고 임계 영역에 진입 

 

B가 먼저 실행된 경우 

-> P 에서 검증을 통과하지 못하고 대기 영역 진입 

-> A 실행 

-> 위의 과정대로 진행

 

 

이렇게 하여 순서에 대한 문제를 해결. 즉 항상 S1이 먼저 수행되도록 함 

 

 

 

자바 구현 코드 

 

1: java.util.conccurent의 Semaphore 사용 

 

package os.semaphore.basic.sync.ex1;

import java.util.concurrent.Semaphore;

class SharedResource {
    private Semaphore semaphore;

    public SharedResource() {
        // 세마포어의 초기값을 0으로 설정하여 동기화 문제를 해결
        semaphore = new Semaphore(0);
    }

    public void processA() {
        try {
            System.out.println("프로세스 A가 작업을 수행합니다.");

            // 프로세스 A 작업 수행

            // 세마포어를 릴리즈하여 프로세스 B가 실행될 수 있도록 함
            semaphore.release();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void processB() {
        try {
            // 세마포어를 획득하여 프로세스 A가 작업을 완료할 때까지 대기
            semaphore.acquire();
            
            System.out.println("프로세스 B가 작업을 수행합니다.");

            // 프로세스 B 작업 수행

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // 프로세스 A 생성 및 실행
        Thread processA = new Thread(() -> {
            sharedResource.processA();
        });

        // 프로세스 B 생성 및 실행
        Thread processB = new Thread(() -> {
            sharedResource.processB();
        });

        // 프로세스 A와 B 실행
        processA.start();
        processB.start();

        // 메인 스레드는 프로세스 A와 B가 모두 종료될 때까지 대기
        try {
            processA.join();
            processB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("작업이 완료되었습니다.");
    }
}

 

 

2. 직접 구현한 세마포어를 사용 

 

package os.semaphore.basic.sync.ex2;

import os.semaphore.Semaphore;

class SharedResource {
    private Semaphore semaphoreMutex;

    public SharedResource() {
        // 세마포어의 초기값을 0으로 설정하여 동기화 문제를 해결
        semaphoreMutex = new Semaphore(0);
    }

    public void processA() {
        try {
            System.out.println("프로세스 A가 작업을 수행합니다.");

            // 프로세스 A 작업 수행

            // 세마포어를 릴리즈하여 프로세스 B가 실행될 수 있도록 함
            semaphoreMutex.release();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void processB() {
        try {
            // 세마포어를 획득하여 프로세스 A가 작업을 완료할 때까지 대기
            semaphoreMutex.acquire();
            
            System.out.println("프로세스 B가 작업을 수행합니다.");

            // 프로세스 B 작업 수행

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();

        // 프로세스 A 생성 및 실행
        Thread processA = new Thread(() -> {
            sharedResource.processA();
        });

        // 프로세스 B 생성 및 실행
        Thread processB = new Thread(() -> {
            sharedResource.processB();
        });

        // 프로세스 A와 B 실행
        processA.start();
        processB.start();

        // 메인 스레드는 프로세스 A와 B가 모두 종료될 때까지 대기
        try {
            processA.join();
            processB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("작업이 완료되었습니다.");
    }
}

 

 

 

 

 

 


참고자료: 운영체제(김진욱, 이인복 공저, KNOU press 출판) 

반응형