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 출판)
'CS > 운영체제' 카테고리의 다른 글
교착상태, 교착상태 특성, 교착상태 예방, 상호배제, 점유대기, 비선점, 환형대기 (0) | 2023.06.15 |
---|---|
병행 프로세스의 여러가지 문제들, 생산자-소비자, 판독기-기록기, 자바 구현 코드, 프로세스 간 통신 (0) | 2023.06.15 |
프로세스 스케줄링, 운영체제 스케줄링 알고리즘 (0) | 2023.06.15 |
운영체제 프로세스와 쓰레드 (0) | 2023.06.14 |
운영체제, 구성, 유형 (0) | 2023.06.14 |