티스토리 뷰
동기화란
다중 스레드나 다중 프로세스 환경에서 공유 자원에 대한 접근을 조절하기 위해 사용되는 기술
임계 구역이란?
임계구역 문제 해결방법
1. 임계 영역엔 동시에 하나의 프로세스만 접근한다.
2. 여러 요청에도 하나의 프로세스의 접근만 허용한다
3. 임계 구역에 들어간 프로세스는 빠르게 나와야한다
Race Condition
경쟁상태 또는 경쟁조건. 멀티스레드 환경에서 쓰레드들이 코드영역, 데이터 영역과 같은 공유자원에 접근할 때, 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태를 말한다.
세마포어란
세마포어란 정수 값을 가지며 두 가지 연산인 P와 V 연산으로 구성된 다중 프로그래밍 환경에서 공유 자원에 대한 접근을 제어하기 위한 동기화 도구 중 하나.
특징
세마포어 변수를 통해 공유자원에 접근할 수 있는 프로세스(스레드)의 수를 제어하고 프로세스의 실행 순서를 원하는 순서로 Ordering할 수 있다.
*세마포어 변수를 통해 실행순서를 원하는 순서대로 ordering해서 얻는 이득?
프로세스 간의 동기화를 보장
1. A와 B가 자원을 사용하는 프로세스이고, A가 먼저 자원을 사용한 후에 B가 자원을 사용해야 한다고 가정
-> 만약 A와 B가 동시에 자원을 사용하려고 한다면, 데이터 충돌이 발생할 수 있다.
2. A가 자원을 모두 사용한 후에 B가 자원을 사용해야 한다.
3.세마포어 변수를 사용하여 이러한 실행 순서를 조정할 수 있다.
종류
- 이진 세마포어: 0과 1의 값만 가능해서, 'Mutex락'과 유사하게 동작하는 세마포어
- 카운팅 세마포어 : 영역(domain)에 제한이 없어 총 가용한 자원의 개수로 초기화되는 세마포어
카운팅 세마포어란?
카운팅 세마포어는 세마포어의 한 종류로, 특정 자원의 사용 가능한 개수를 나타내는 변수.
이 변수는 초기값을 가지며, 자원을 사용할 때마다 값을 감소시키고, 자원을 반납할 때마다 값을 증가시킨다.
값이 0인 경우에는 자원을 사용할 수 없고, 값이 양수인 경우에는 자원을 사용할 수 있는 개수를 나타낸다.
카운팅 세마포어는 여러 개의 프로세스나 스레드가 동시에 자원을 사용할 수 있는 경우에 유용하게 사용됩니다.
1. 어떤 자원이 3개 있다면, 카운팅 세마포어의 초기값을 3으로 설정합니다.
2. 이후에 자원을 사용하면, 세마포어 변수의 값을 1 감소시킴
2-1 자원을 반납하면 세마포어 변수의 값을 1 증가시킵니다.
2-2.만약 세마포어 변수의 값이 0인 경우에는 자원을 사용할 수 없으므로 대기 상태로 들어가게 됩니다.
=> 여러 개의 프로세스나 스레드가 동시에 자원을 사용할 때, 자원의 사용 가능한 개수를 조절할 수 있습니다.
사용하는 경우
여러 개의 스레드가 동시에 특정 자원을 사용해야 하는 경우, 카운팅 세마포어를 사용하여 자원의 사용 가능한 개수를 조절할 수 있습니다. 이를 통해 스레드 간의 경쟁 상황을 줄이고, 자원의 사용을 효율적으로 관리할 수 있습니다.
쓰는 이유
세마포어(Semaphore)는 병렬 프로그래밍에서 공유 자원에 대한 접근을 제어하기 위한 동기화 도구입니다.
여러 개의 스레드나 프로세스가 공유 자원에 동시에 접근할 때, 세마포어를 사용하여 상호 배제(Mutual Exclusion) (하나의 프로세스가 임계 구역에 들어가 있을 때 다른 프로세스의 접근을 막는 것)를 구현할 수 있습니다.
Block & wake-up 방식
Block & wake-up 방식은 대기 상태에 있는 스레드를 블록 상태로 만드는 것이 특징입니다. 이 방식에서는 블록된 스레드가 대기 큐(Wait Queue)에 저장된다. 세마포어의 값을 변경하여 깨우는 대기 중인 스레드는 대기 큐에서 제거된다.
이 방식은 블록된 스레드가 많아질수록 성능이 저하될 가능성이 있습니다. 또한, 깨우는 스레드가 블록된 스레드보다 먼저 실행되어 대기 큐가 계속 쌓이는 경우, 세마포어의 사용 효율이 떨어질 수 있습니다.
구현방식(작동방식)
- 세마포어를 정수형 변수로 선언합니다.
- 세마포어를 사용하는 스레드는 P 연산을 수행합니다.
- 세마포어의 값이 0일 경우, 스레드는 블록 상태로 전환됩니다.
- 세마포어의 값이 0보다 큰 경우, 세마포어의 값을 1 감소시키고, 스레드는 임계 영역을 실행합니다.
- 임계 영역 실행이 끝나면 세마포어의 값을 1 증가시키고, 대기 중인 스레드 중 하나를 깨웁니다.
연산 종류
P 연산 : 자원의 여분이 없다면, Block
V 연산 : 자원의 여분이 있다면, 바로 획득.-> 자원을 다쓴후 기다리는 프로세스에게 깨워서 줌(wake up)
- P (Proberen): 세마포어 값을 1 감소시키는 연산입니다. 값이 0 이상인 경우 바로 감소시키고, 값이 음수인 경우 세마포어 값이 양수가 될 때까지 대기합니다.
- V (Verhogen): 세마포어 값을 1 증가시키는 연산입니다. 값이 0 이상인 경우 바로 증가시키고, 값이 음수인 경우 대기 중인 스레드 중 하나를 깨웁니다.
작동방식
- 공유 자원을 접근하려는 스레드는 P 연산을 이용하여 세마포어 값을 감소시킵니다.
- 만약 세마포어 값이 0 이상인 경우, 스레드는 공유 자원에 접근할 수 있습니다. 이때 다른 스레드는 P 연산을 대기하게 됩니다.
- 공유 자원 사용이 끝나면, 스레드는 V 연산을 이용하여 세마포어 값을 증가시킵니다.
- 만약 세마포어 값이 음수인 경우, 대기 중인 스레드 중 하나를 깨워 공유 자원에 접근할 수 있게 합니다.
상태값
Block : 세마포어를 획득할 수 없으면 Block 시킴
Wakeup : 누군가가 세마포어를 쓰고 반납시 block된 프로세스중 하나를 깨워서 wake up을 시킴
장/단점
단점 :
- process상태를 변화시키는 작업(ready -> sleep, block->ready) 이 overhead가 커서 Critical Section이 짧을땐 적절하지 않음
- 데드락 : 세마포어의 단점1, 둘 이상의 프로세스가 서로 상대방에 의해 충돌
- Starvation :세마포어의 단점2, 특정 프로세스가 영원히 자원을 얻지못하고 영원히 기다려야하는 문제
장점 :
- 공유자원 접근제어
- 데드락 방지 (여러 스레드가 공유 자원에 접근하는 순서를 조정할 수 있기 때문)
- 우선순위 지정
- 효율성
Dining-Philosophers Problem
다섯 명의 철학자들이 한 원탁에 앉아 젓가락을 사용하여 식사를 하는 상황
- 각 철학자들은 자신의 앞과 뒤에 있는 두 개의 젓가락 중 하나만 사용
- 식사를 마치면 사용한 젓가락을 다시 테이블에 놓음
문제점
- 교착 상태(Deadlock): 모든 철학자가 한 손에 젓가락을 들고 기다리며 다른 한 손에 젓가락을 기다리는 경우 발생
- 경쟁 상태(Race Condition): 두 명 이상의 철학자가 동시에 같은 젓가락을 집으려고 하는 경우 발생
해결방안
뮤텍스와 세마포어 등의 동기화 도구를 사용하여 각 철학자가 젓가락을 집을 때 다른 철학자들이 집지 못하도록 잠금(lock)을 거는 방식
-> 1.젓가락을 들어야만 식사를 시작할 수 있음
2. 각 철학자는 자신의 왼쪽과 오른쪽의 젓가락이 모두 사용 가능할 때만 두 개의 젓가락을 동시에 집을 수 있다.
뮤텍스란
쓰는 이유
- 뮤텍스는 이진 세마포어와 유사한 역할을 하며, 상호 배제를 구현하는 데에 사용
- 락(Lock)이라는 개념을 도입하여 락을 소유하고 있는 스레드나 프로세스만이 뮤텍스를 해제할 수 있음
- 뮤텍스는 세마포어보다 간단하고 빠르게 동작하며, 특히 상호 배제를 구현할 때 사용
- 뮤텍스는 Windows 등에서는 기본적으로 제공되는 동기화 도구 -> 다른 운영 체제나 환경에서 세마포어를 사용하기 어려운 경우에도 뮤텍스를 사용할 수 있다.
상호배제란
병렬 프로그래밍에서 공유 자원에 대한 동시 접근을 막는 기법
구현방식(작동방식)
- 뮤텍스 객체를 생성
- 임계 영역에 진입하기 전에 뮤텍스 객체를 잠근다.
- 임계 영역에서 작업을 수행
- 임계 영역에서 빠져나온 후에 뮤텍스 객체를 잠금 해제한다.
- 다음 스레드가 임계 영역에 접근할 때까지 대기한다.
Test-and-Set 방식
Test-and-Set 방식은 뮤텍스를 구현하는 가장 기본적인 방법
이 방식은 뮤텍스의 상태를 나타내는 변수를 사용하며, 뮤텍스를 획득하고자 하는 스레드는 이 변수의 값을 변경하면서 뮤텍스를 획득하게 됩니다.
- 뮤텍스를 표현하는 상태 변수와 이 변수를 변경하는 명령어인 Test-and-Set 명령어를 사용합니다.
- 뮤텍스를 사용하려는 스레드는 먼저 뮤텍스의 상태 변수를 읽어옵니다.
- 상태 변수가 0인 경우, 해당 스레드는 뮤텍스를 획득합니다.
- 상태 변수가 1인 경우, 해당 스레드는 뮤텍스를 획득할 때까지 대기합니다.
- 뮤텍스를 획득한 스레드는 자원에 접근하고, 작업을 수행합니다.
- 작업이 끝나면 뮤텍스를 반납합니다. 이때, 상태 변수를 0으로 변경하여 다른 스레드가 뮤텍스를 획득할 수 있도록 합니다.
import java.util.concurrent.atomic.AtomicBoolean;
public class Mutex {
private AtomicBoolean lock = new AtomicBoolean(false);
public void acquire() {
// 뮤텍스 획득
while (lock.getAndSet(true));
}
public void release() {
// 뮤텍스 반납
lock.set(false);
}
}
*자바에서 뮤텍스는 java.util.concurrent 패키지에서 제공되는 Lock 인터페이스와 그 구현체인 ReentrantLock 클래스를 이용하여 구현할 수 있다.
PrintThreadId 클래스에서는 ReentrantLock 객체를 이용하여 뮤텍스를 구현하고, lock() 메서드로 뮤텍스 객체를 잠그고, 스레드 ID를 출력한 후 unlock() 메서드로 뮤텍스 객체를 잠금 해제
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class PrintThreadId implements Runnable {
private static final Lock mutex = new ReentrantLock(); // 뮤텍스 객체 생성
private final int id;
public PrintThreadId(int id) {
this.id = id;
}
@Override
public void run() {
mutex.lock(); // 뮤텍스 객체를 잠근다.
System.out.println("thread #" + id);
mutex.unlock(); // 뮤텍스 객체를 잠금 해제한다.
}
}
public class Main {
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new PrintThreadId(i));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
뮤텍스와 세마포어의 차이점은 무엇인가요?
세마포어 | 뮤텍스 | |
공유자원에 접근갯수 | 세마포어 변수만큼 | 1개만의 프로세스/쓰레드 |
해제할 수 있는 자원 | 다른 자원이 세마포어 해제 가능 | 락을 걸은 프로세스만 해제 가능 |
뮤텍스와 세마포어는 언제 쓰는가
세마포어 | 뮤텍스 | |
공통점 | 모두 동시에 실행되는 프로세스나 스레드들 간에 공유된 자원을 보호하기 위해 사용 * 두 기법은 경우에 따라 교차적으로 사용될 수 있다. |
|
차이점 | 1.자원의 사용 가능한 개수를 나타내는 변수를 가짐 2.여러 개의 스레드가 동시에 자원을 사용할 수 있는지를 결정 |
1.상호배제(mutex)의 기능을 가짐 2. 오직 하나의 스레드만 뮤텍스를 보유 |
특징 | 여러 개의 스레드가 자원을 공유할 수 있는 경우에 사용(여러 개의 자원을 공유) | 대개 하나의 자원을 보호하고자 할 때 사용(하나의 자원을 보호) |
모니터란
내부의 프로시저를 통해 하나의 스레드만 공유 자원에 접근이 가능하도록 하고 조건 변수(Condition Variable)를 사용하여 스레드를 대기 시키고 깨울 수 있다.
*모니터가 추상데이터 타입인 이유
*공유 자원에 대한 접근을 제한하는 메소드와 조건 변수를 사용하여 동기화를 수행 한다는 뜻은?
- produce 메소드는 생산자 스레드가 공유 자원인 버퍼에 데이터를 추가하는 방법을 제어
consume 메소드는 소비자 스레드가 공유 자원인 버퍼에서 데이터를 가져오는 방법을 제어
(아래 예시 참고)
- 조건변수
조건 변수는 특정 조건이 충족되기 전까지 프로세스 또는 스레드를 대기 상태로 만듭니다.
예를 들어, 공유 큐에 데이터가 없는 경우 소비자 프로세스는 큐에서 데이터를 읽을 수 없으며, 조건 변수를 사용하여 대기하게 됩니다. 생산자 프로세스가 큐에 데이터를 추가하면 조건이 충족되고 소비자 프로세스가 깨어나 큐에서 데이터를 가져옵니다.
쓰는이유
- 객체 지향 프로그래밍 언어에서 제공하는 동기화 기법
- -> 락(lock)과 언락(unlock)을 자동으로 처리하는 개념적인 동기화 블록으로 구현
- 모니터 내부에서 락(lock)과 언락(unlock) 처리를 자동으로 수행
- -> 코드 작성이 간단해지고 실수를 줄일 수 있음
- 결론 : 객체 지향 언어에서 구현되고 간단하고 안전한 사용을 보장
구현방식(작동방식)
- 스레드가 모니터에 진입하려고 할 때, 락을 획득
- 다른 스레드가 이미 모니터의 락을 획득한 상태라면, 현재 스레드는 대기
- 대기 중인 스레드는 모니터 내부의 준비 큐에 저장
- 준비 큐에서는 스레드를 일시 중지시키고, 대기 중인 스레드들의 목록을 유지
- 모니터 내의 조건 변수를 사용하여 특정 조건이 충족될 때까지 프로세스를 대기시킬 수 있다. -> 이 경우, 프로세스는 조건 대기 큐(condition wait queue)에서 대기
- 스레드가 일시 중지된 상태에서 해당 조건이 충족되면, 모니터는 대기 중인 스레드를 깨우고 다시 실행될 수 있도록 함
- 스레드가 모니터를 빠져나갈 때, 락을 반납합니다. 이때 다른 스레드들이 모니터에 진입할 수 있음
위 예제에서 produce 메소드는 생산자 스레드가 공유 자원인 버퍼에 데이터를 추가하는 방법을 제어합니다. 메소드는 synchronized 키워드를 사용하여 동시에 여러 스레드가 접근하지 못하도록 보장합니다. 버퍼가 가득 찬 경우, wait() 메소드를 호출하여 생산자 스레드가 대기하게 하며, 버퍼에 공간이 생기면 notifyAll() 메소드를 통해 대기 중인 스레드를 깨웁니다.
consume 메소드는 소비자 스레드가 공유 자원인 버퍼에서 데이터를 가져오는 방법을 제어합니다. 이 메소드 역시 synchronized 키워드를 사용하여 동시 접근을 제한합니다. 버퍼가 비어 있는 경우, wait() 메소드를 호출하여 소비자 스레드가 대기하게 하며, 버퍼에 데이터가 추가되면 notifyAll() 메소드를 통해 대기 중인 스레드를 깨웁니다.
//공유 버퍼를 관리하는 모니터 클래스를 생성합니다.
import java.util.LinkedList;
import java.util.Queue;
public class BufferMonitor {
private final Queue<Integer> buffer;
private final int capacity;
public BufferMonitor(int capacity) {
this.buffer = new LinkedList<>();
this.capacity = capacity;
}
public synchronized void produce(int item) throws InterruptedException {
while (buffer.size() >= capacity) {
wait();
}
buffer.add(item);
System.out.println("Produced: " + item);
notifyAll();
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait();
}
int item = buffer.poll();
System.out.println("Consumed: " + item);
notifyAll();
return item;
}
}
//생산자 스레드와 소비자 스레드를 생성하는 클래스를 만듭니다
class Producer extends Thread {
private final BufferMonitor bufferMonitor;
public Producer(BufferMonitor bufferMonitor) {
this.bufferMonitor = bufferMonitor;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
bufferMonitor.produce(i); //공유 버퍼에 데이터를 추가
Thread.sleep(500); //500ms 동안 일시 중지하여 시간 지연을 흉내냄
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer extends Thread {
private final BufferMonitor bufferMonitor;
public Consumer(BufferMonitor bufferMonitor) {
this.bufferMonitor = bufferMonitor;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
bufferMonitor.consume();
Thread.sleep(1000); //시간 지연을 흉내냄
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//메인 클래스에서 생산자 스레드와 소비자 스레드를 생성하고 실행합니다.
public class Main {
public static void main(String[] args) {
BufferMonitor bufferMonitor = new BufferMonitor(5);
Producer producer = new Producer(bufferMonitor);
Consumer consumer = new Consumer(bufferMonitor);
producer.start();
consumer.start();
}
}
뮤텍스와 모니터의 차이점은 무엇인가요?
세마포어와 모니터의 차이점은 무엇인가요?
세마포어 | 모니터 | |
특징 | - 낮은 수준의 동기화 기본 요소로, 정수 값을 가진 변수와 두 가지 원자적 연산 (wait, signal)으로 구성 |
- 높은 수준의 동기화 추상화로, 객체 지향 프로그래밍 개념을 사용하여 공유 자원에 대한 접근을 제어 |
구현 및 사용 방법 | 운영 체제의 커널이 제공하는 기능을 사용하여 구현 wait과 signal 연산을 사용하여 동기화를 수행 |
프로그래밍 언어 레벨에서 제공되는 추상 데이터 타입으로, 공유 자원에 대한 접근을 제한하는 메소드와 조건 변수를 사용하여 동기화를 수행 |
단점 | 세마포어는 프로그래머가 직접 동기화를 제어해야 하므로, 오류 발생 가능성이 높음 | |
장점 | 모니터는 상호 배제와 조건 변수를 사용해 동기화를 자동으로 처리하므로, 프로그래머의 실수 가능성이 낮아짐 |
요즘은 특정 동기화 도구만 쓰는가?
세마포어는
- 여러 개의 스레드나 프로세스가 동시에 접근할 수 있는 동기화 도구
- 공유 자원의 접근 허용 개수를 조절하여 여러 개의 스레드나 프로세스가 동시에 공유 자원에 접근
- 이러한 특징으로 인해 세마포어는 뮤텍스보다 복잡한 상황에서 더 유용하게 사용될 수 있습니다.
뮤텍스는
- Windows, Linux 등에서 기본적으로 제공되는 동기화 도구
- 세마포어나 모니터 등의 다른 동기화 도구와 비교해서도 많이 사용
모니터는
- 객체 지향 프로그래밍에서 사용되는 동기화 도구
- 뮤텍스와 세마포어를 포함한 다른 동기화 도구들의 기능을 하나로 묶어서 제공
- 모니터는 공유 자원에 대한 접근을 관리하는 데에 뮤텍스와 유사한 방식을 사용하며, 락을 획득한 스레드만이 모니터의 메서드를 호출할 수 있습니다.
- 모니터는 뮤텍스와 세마포어보다 더 추상적인 개념이며, 객체 지향 프로그래밍에서 더욱 직관적인 코드 작성을 가능하게 해줍니다.
따라서, 뮤텍스, 세마포어, 모니터 등의 동기화 도구들은 상황과 용도에 따라 적절하게 선택하여 사용해야 합니다.
왜 뮤텍스는 Windows, Linux 등에서 기본적으로 제공되는 동기화 도구인가?
운영체제는 하드웨어와 소프트웨어 사이에서 중재자 역할을 수행(프로세스/쓰레드 관리, 서비스 제공, 자원 관리 등) 역할 때문에 운영체제는 시스템의 안전성과 보안성을 보장해야 함
-> 안전하게 자원을 공유하고, 경쟁 상태를 방지하여 안정적인 시스템 동작을 보장해야함
-> 뮤텍스(Mutex)와 같은 동기화 기법을 사용하여 다수의 프로세스나 쓰레드가 동시에 자원에 접근하는 경우, 이를 동기화하여 경쟁 상태를 방지
출처
https://www.shekhali.com/c-monitor-class-in-multithreading-with-examples/
'OS' 카테고리의 다른 글
[OS] DeadLock(데드락) (0) | 2023.03.19 |
---|---|
[OS] Process 동기화 - 싱글코어가 아니라 멀티코어라면, 어떻게 동기화가 이뤄질까요? (3) | 2023.03.14 |
[OS] Process (0) | 2023.03.06 |
[OS] CPU Scheduler (1) | 2023.03.06 |
[OS] 쓰레드와 TCB (0) | 2023.02.28 |