Operation System - 공유 자원, 임계 영역

Tae Yun Choi·2023년 5월 9일
0
post-thumbnail

공유 자원

  • 공유 자원은 시스템 내에서 프로세스, 스레드가 함께 접근할 수 있는 자원 및 변수 등을 의미함
  • 공유 자원에 대해 동시에 읽거나 쓰는 상황을 경쟁 상태(race condition)이라고 함
  • 경쟁 상태에 의한 주요 문제점
    • 상호 배제의 필요성
    • 교착 상태
    • 기아 상태
  • 즉, 접근 타이밍 및 순서 등이 결과에 영향을 주는 상태

임계 영역

  • 경쟁 상태에 의해 영향을 받는 영역을 임계 영역(critical section)이라고 함
  • 임계 영역 해결 위한 세가지 방법
    • 뮤텍스 (mutex)
    • 세마포어 (semaphore)
    • 모니터 (monitor)
    • 위 세가지는 모두 상호 배제, 한정 대기, 융통성이라는 조건을 만족함
      • 상호배제(mutex) - 한 프로세스가 임계 영역에 들어갔을 때 다른 프로세스는 들어갈 수 없음
      • 한정대기 - 특정 프로세스가 영원히 임계 영역에 들어가지 못하는 것은 안 됨
      • 융통성 - 한 프로세스가 다른 프로세스의 일을 방해하면 안 됨
  • 해결 방법의 기본 메커니즘은 잠금(lock)을 활용하는 것

뮤텍스 (mutex)

  • 동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘
  • 공유된 자원의 데이터 혹은 임계영역(Critical Section) 등에 하나의 Process 혹은 Thread가 접근하는 것을 막아줌 (동기화 대상이 하나)
  • 다른 프로세스나 스레드 간 동기화를 위해 사용
  • 공유 자원을 사용하기 전에 설정하고 사용한 후에 해제하는 잠금을 의미
  • 잠금이 설정되면 다른 스레드는 잠긴 코드 영역에 접근할 수 없음
  • 뮤텍스는 하나의 상태(잠금 or 해제)만 가짐
  • 무겁고 느리다

상호배제를 보장하는 뮤텍스의 예시

package util.jsonparser;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MutexExample {
    private static Lock mutex = new ReentrantLock(); // 뮤텍스 생성

    public static void main(String[] args) {
        // 스레드 생성
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mutex.lock(); // 뮤텍스 락 획득
                try {
                    // 뮤텍스를 획득한 스레드가 수행할 작업
                    System.out.println("Thread 1 acquired the mutex");
                    Thread.sleep(2000); // 작업 수행 중 일부러 지연시킴
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    mutex.unlock(); // 뮤텍스 락 해제
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mutex.lock(); // 뮤텍스 락 획득
                try {
                    // 뮤텍스를 획득한 스레드가 수행할 작업
                    System.out.println("Thread 2 acquired the mutex");
                } finally {
                    mutex.unlock(); // 뮤텍스 락 해제
                }
            }
        });

        // 스레드 실행
        thread1.start();
        thread2.start();
    }
}

상호배제를 보장하기 위한 하드웨어적인 접근 방법 예시

/* program mutualexclusion */
**/* Compare and swap instruction */**
const int n = /* number of processes */;
int bolt;
void P(int i)
{
		while (true) {
				// bolt와 0이 같으면 1을 반환, 다르면 0을 반환
				while (compare_and_swap(&bolt, 0, 1) == 1)
						/* do nothing */;
				/* critical section */;
				bolt = 0;
				/* remainder */;
		}
}

void main()
{
		bolt = 0;
		parbegin (P(1), P(2), . . . ,P(n));
}

int compare_and_swap (int *word, int testval, int newval)
{
		int oldval;
		oldval = *word;
		if (oldval == testval)
				*word = newval;
		return oldval;
}
/* program mutualexclusion */
**/* Exchange instruction */**
int const n = /* number of processes*/;
int bolt;
void P(int i)
{
		while (true) {
				int keyi = 1;

				// keyi와 bolt의 값을 계속 교체
				do exchange (&keyi, &bolt)
				while (keyi != 0);
				/* critical section */;
				bolt = 0;
				/* remainder */;
		}
}
void main()
{
		bolt = 0;
		parbegin (P(1), P(2), . . ., P(n));
}

void exchange (int *register, int *memory)
{
		int temp;

		temp = *memory;
		*memory = *register;
		*register = temp;
}

세마포어 (semaphore)

  • 멀티 프로그래밍 환경에서 공유된 자원에 대한 접근을 제한하는 방법
  • 공유된 자원의 데이터 혹은 임계영역(Critical Section) 등에 여러 Process 혹은 Thread가 접근하는 것을 막아줌 (동기화 대상이 하나 이상)
  • 세마포어는 일반화 된 뮤텍스이며, 원자적 함수로 조작되는 정수 변수
  • 간단한 정수 값 + 두 가지 함수(wait, signal)로 공유 자원에 대한 접근 처리
  • wait()은 자신의 차례 올 때까지 기다리는 함수
  • signal()은 다음 프로세스로 순서를 넘겨주는 함수
    • 프로세스가 공유 자원에 접근 시 세마포어에서 wait() 작업을 수행하고
    • 프로세스가 공유 자원을 해제하면 세마포어에서 signal() 작업을 수행
    • 세마포어는 조건 변수가 없고, 프로세스가 세마포어 값을 수정할 때 다른 프로세스는 동시에 세마포어 값을 수정할 수 없음

바이너리 세마포어

  • 바이너리 세마포어는 0과 1의 두 가지 값만 가지는 세마포어
  • 구현의 유사성으로 인해 뮤텍스는 바이너리 세마포어라고 할 수 있음 (두 가지 상태만 가지기 때문)
  • 하지만 엄밀히 말하면, 뮤텍스는 리소스에 대한 접근을 동기화 하는데 사용되는 잠금이고,
  • 세마포어는 신호를 기반으로 상호 배제가 일어나는 신호
typedef struct {
	_Bool value;
	queue waitQueue;
} binary_semaphore;

void semInitB(binary_semaphore s, int n)
{
	s.value = n;
}

void semWaitB(binary_semaphore s)
{
	if(s.value == 1)
		s.value = 0;
	else{
		//요청한 thread를 s.waitQueue에 push
		//요청한 thread의 상태를 block으로 변경
	}
}

void semSignalB(binary_semaphore s)
{
	if(s.waitQueue.empty())
		s.value = 1;
	else{
		//s.waitQueue에서 thread 1개를 pop
		//pop한 thread의 상태를 runnable로 변경 후 OS의 readyQueue에 push
	}
}

카운팅 세마포어 (범용 세마포어)

  • 카운팅 세마포어는 여러 개의 값을 가질 수 있는 세마포어이며, 여러 자원에 대한 접근 제어할 때 사용
typedef struct{
	int count;
	queue waitQueue;
} semaphore;

void semInit(semaphore s, int n)
{
	s.count = n;
}

void semWait(semaphore s)
{
	s.count--;
	if(s.count < 0){
		//요청한 thread를 s.waitQueue에 push
		//요청한 thread의 상태를 block으로 변경
	}
}

void semSignal(semaphore s)
{
	s.count++;
	if(s.count <= 0){
		//s.waitQueue에서 thread 1개를 pop
		//pop한 thread의 상태를 runnable로 변경 후 OS의 readyQueue에 push
	}
}

세마포어를 이용한 Mutex 예시

semaphore s;

void thread_execute(int thread_no)
{
	while(1){
		semWait(s);
		//임계 영역
		semSignal(s);
		//임계 영역 이후
	}
}

int main(void)
{
	semInit(s, 1);
	for(int i = 0; i < num_of_threads; i++)
		thread_start(i);
}
import java.util.concurrent.Semaphore;

public class BinarySemaphoreExample {
    private static Semaphore semaphore = new Semaphore(1); // 이진 세마포어 생성

    public static void main(String[] args) {
        // 스레드 생성
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire(); // 세마포어 획득
                    System.out.println("Thread 1 acquired the semaphore");
                    Thread.sleep(2000); // 작업 수행 중 일부러 지연시킴
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 세마포어 해제
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore.acquire(); // 세마포어 획득
                    System.out.println("Thread 2 acquired the semaphore");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 세마포어 해제
                }
            }
        });

        // 스레드 실행
        thread1.start();
        thread2.start();
    }
}

모니터

  • 모니터란 세마포어를 실제로 구현한 프로그램
  • 모니터는 둘 이상의 스레드나 프로세스가 공유 자원에 안전하게 접근 가능하도록 공유 자원을 숨기고, 해당 접근에 대한 인터페이스만 제공
  • 하나의 프로세스 내에서 다른 스레드 간 동기화 할 때 사용
  • 프레임워크나 라이브러리 그 자체에서 제공됨
  • 가볍고 빠름

  • 모니터는 세마포어보다 구현하기 쉬우며 모니터에서 상호 배제는 자동인 반면, 세마포어에서는 상호 배제를 명시적으로 구현해야 하는 차이점이 존재

Mutex vs Mutual Exclusion

  • Mutex는 Mutual Exclusion의 줄임 말로 임계 구역에 동시에 접근하지 못하도록 막는 기법을 의미
  • 일반적으로 프로그래밍에서 Mutex를 사용하여 상호배제를 만족시킨다고 가정할 때 뮤텍스는 한 프로세스의 내부에서 여러 스레드의 임계구역 제어를 위해 사용하는 객체를 뜻한다.

예상 질문

  • 경쟁 상태가 무엇인가요?
  • 뮤텍스와 세마포어에 대해서 설명해주세요.
    • 둘다 운영체제에서 동기화를 달성하기 위한 장치
    • 세마포어는 뮤텍스가 될 수 있지만, 뮤텍스는 세마포어가 될 수 없음
    • 세마포어는 소유할 수 없으며, 뮤텍스는 소유할 수 있고 소유주가 그에 대한 책임을 가짐
    • 세마포어는 동기화 대상이 여러개 일 때 사용하고, 뮤텍스는 동기화 대상이 오로지 하나 일 때 사용

출처

profile
hello dev!!

0개의 댓글