[OS] Semaphore, Ordering, Mutex (은행 계좌 자바 예제)

2021. 6. 13. 20:36IT

SMALL

0. Semaphore

  • 여러 개의 프로세스가 공유 자원에 동시 접근할 때 문제 발생
    • 프로세스 A, 프로세스 B는 int a = 100 이라는 자원을 공유하여, 각각 a를 1씩 증가시키는 작업을 시킴
    • 이때, 한번에 둘 이상의 프로세스가 접근하여 데이터를 동시에 변경하며 결과값에 영향을 줄 수 있음
      • 경쟁 상태= Race condition
    • 공유된 자원 속 하나의 데이터는 한번에 프로세스만 접근할 수 있도록 제한을 두어야 할 때 고안된 것이 Semaphore (쎄마포어)

 

  • Semaphore는 P연산과 V연산으로 이루어져 있음
    • P(S) : S를 1 감소 (S--)
    • V(S) : S를 1 증가 (S++)
  • 프로세스는 S가 1 이상일 때만 임계영역으로 진입할 수 있다고 가정 / S가 0이면 진입하지 못함

 

* Semaphore in JAVA 

  1. acquire 메서드
    1. lock 확보
    2. 세마포어는 S를 검사
      1. 만약 S가 1 이상이면 스레드 임계구역 진입 허가
      2. 만약 S가 1보다 작으면 리스트에 저장 후 일시 정지
  2. release 메서드
    1. 공유자원을 점유한 스레드가 임계구역에서 나올 경우
      1. S++
    2. 대기 중인 스레드 중에서 하나를 깨우고, 깨어난 스레드는 임계구역에 들어감

 

은행 계좌 문제 (자바 코드)

Deposit.java (예금)

public class Deposit implements Runnable {
	Account account; // 공유 객체
	
	public Deposit(Account account) {
		this.account = account;
	}
	@Override
	public void run() {
		for(int i=0; i<Bank.LOOP; i++) {
			account.deposit(10);
		}
	}
}

WithDraw.java (인출)

public class WithDraw implements Runnable {
	Account account;
	public WithDraw(Account account) {
		this.account = account;
	}
	@Override
	public void run() {
		for(int i=0; i<Bank.LOOP; i++) {
			account.withDraw(10);
		}
	}
}

Account.java (계좌)

import java.util.concurrent.Semaphore;
public class Account {
	private int balance = 0; // 잔액
	Semaphore s, with, depo; // 세마포어 객체 참조변수
	
	public Account(Semaphore s, Semaphore with, Semaphore depo) {
		this.s = s;
		this.with = with;
		this.depo = depo;
	}
	// deposit- 예금
	public void deposit(int money) {
		try {
			s.acquire();// 세마포어 객체를 통한 동기화 검사
			// S를 검사함. S가 1이면 스레드가 CS에 들어가는 것을 허용
			// 임계 영역(critical section)에 들어가서
			System.out.println(Thread.currentThread().getName()+" : " + money+"원");
			balance += money;
			System.out.println("현재 잔액 : " + balance + "원");
			System.out.println();
			s.release(); // Lock 해제
			with.release(); 
			depo.acquire(); // 입금후에는 반드시 출금을 해야 하므로 자신을 블록함
		} catch (InterruptedException e) {} 
	}
	// withDraw - 인출
	public void withDraw(int money) {
		try {
			with.acquire(); // 세마포어 변수 0이므로 대기. 입금보다 먼저 수행하는 것을 막음
			s.acquire(); // 세마포어 객체를 통한 동기화 검사
			// 임계영역
			System.out.println(Thread.currentThread().getName() + " : " + money + "원");
			balance -= money;
			System.out.println("현재 잔액 : " + balance + "원");
			System.out.println();
			s.release(); // Lock 해제
			depo.release(); 
			// 출금 수행이 완료되면 block되었던 입금 프로세스를 깨워줌
		}
		catch(InterruptedException e) {} }
	
	public void printBalance() {
		// 잔액 출력
		System.out.println("현재 잔액 : " + balance);
	}
}

 

Bank.java (은행)

import java.util.concurrent.Semaphore; 
public class Bank {
	static final int LOOP = 2;
	public static void main(String[] args) {
		// 세마포어 객체 생성 (permit = 1: 공유자원 1개, fair = true : FIFO)
		Semaphore s = new Semaphore(1, true); 
		Semaphore s_1 = new Semaphore(0, true); 
		Semaphore s_2 = new Semaphore(0, true); 
		Account account = new Account(s, s_1, s_2); // 공유 객체 생성
		// 스레드 생성
		Thread depositThread1 = new Thread(new Deposit(account));
		Thread withDrawThread1 = new Thread(new WithDraw(account));
		Thread depositThread2 = new Thread(new Deposit(account));
		Thread withDrawThread2 = new Thread(new WithDraw(account));
		
		// 스레드 이름 설정
		depositThread1.setName("user1 입금");
		withDrawThread1.setName("user1 출금");

		
		depositThread2.setName("user2 입금");
		withDrawThread2.setName("user2 출금");
		
		// 스레드 실행
		depositThread1.start();
		withDrawThread1.start();
		depositThread2.start();
		withDrawThread2.start();
		
		// 스레드 정지
		try {
			// join을 사용하지 않는다면
			// 메인시작 ~ 메인 끝나도 스레드 백그라운드에서 돌아감. 메인은 상관없이 종료
			// join을 사용한다면
			// 스레드 끝날떄까지 기다렸다가 메인이 끝남
			
			depositThread1.join();
			withDrawThread1.join();
			depositThread2.join();
			withDrawThread2.join();
		}
		catch(InterruptedException e) {}
		
		// 잔액 출력
		account.printBalance();
	}

잠깐! Ordering이란 ❔❓ 

- 입금 출금 순서가 안 지켜졌을 경우

- 혹시 아래 Account.java 코드에서

deposit 메소드의 with.release(), depo.acquire(); 와

withDraw 메소드의 with.acquire(); deop.release(); 코드 보이시나요? (가독성 죄송합니다..;0;)

이 네개를 일단 처음에 쓰지 않고 Bank.java에서 print를 실행하면

총 잔액이 0원이 되긴 하지만 그 과정에서 user1, user2 뿐만 아니라 입금 출금의 순서가 뒤죽박죽인 걸 볼 수 있습니다!

우리의 통장이 마이너스 통장이 되는 걸 그냥 보고만 있을 수 없죠!!

이때, Ordering Semaphore가 필요합니다.

 

Ordering 적용 X / 마이너스 계좌

 

다시 위의 deposit 메소드의 with.release(), depo.acquire(); 와

withDraw 메소드의 with.acquire(); deop.release(); 코드 를 그대로 적용해 보시면

user1 입금 user2 입금 user1 출금 user2 출금 순으로 잘 작동되는 걸 보실 수 있습니다.

deposit 메소드에선

Semaphore 변수(S)가 0인 with.release()를 해주어 출금하라고 출금 메소드를 깨워주고(s++)

depo.acquire()는 변수가 0이므로 출금을 하려고 하면 일시정지를 하게 하는 거죠!!

마찬가지로 withdraw(인출) 메소드에선

Semaphore 변수(S)가 0인 with.acquire()를 통해 출금을 먼저 하려고 하면 일시 정지하게 하고

depo.release()를 해 주어 출금 수행이 완료되면 잠자고 있던 입금 프로세스를 깨워줍니다

Ordering 적용

 

1. Mutex vs Semaphore

Mutex
 - lock, unlock의 상태만 존재하는 일종의 key를 기반으로 하는 이진 세마포어
Semaphore
 - 여러개의 프로세스가 동시에 공유자원에 접근할 수 있음
 - S =2 , 임계영역에 들어갈 수 있는 프로세스는 2개가 됨

 

LIST