동기화란
동시다발적으로 실행되는 많은 프로세스는 서로 데이터를 주고 받으며 협력하며 실행될 수 있는데 이렇게 협력적으로 실행되는 프로세스들은 아무렇게나 실행되서는 안된다. 국어 사전에 따른 동기화란 정보 통신 분야에서의 작업들 사이의 수행 시기를 맞추는 것을 의미한다고 나와있다. 프로세스 동기화는 실행 순서의 제어와 상호배제를 의미 한다.
Race Condition
두개 이상의 프로세스가 공통 자원을 동시에 읽거나 쓰는 동작을 할때, 잘못된 실행 순서 실행 등으로 인해 여러 프로새세스가 동시 다발적으로 코드를 실행하여 결과가 달라지는 상황을 말한다. Race Conditon을 막기위해 프로세스 간의 실행 순서를 정해주는 동기화가 필요하다.
발생 상황
- 커널 작업중 인터럽트가 발생할때
- 시스템 콜을 통한 문맥 교환
- 여러 사용자나 서비스가 동시에 같은 캐시 항목을 업데이트하려고 시도할 때.
임계 구역
두개 이상의 스레드나 프로세스가 동시에 접근하면 안되는 영역을 의미한다.
동시에 여러개의 쓰레드나 프로세스가 접근하게 되면 위에 소개한 RaceCondion 상황이 발생하여 일관성이 깨진다.
임계 구역의 특징
- 상호 배제 : 임계 구역에는 한번에 하나의 스레드나 프로세스만 들어갈 수 있어야 한다.
- 진행 : 임계 구역에 어떤 프로세스도 집입하지 않았다면 임계구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
- 유한 대기 : 임계 구역에 진입하고 싶은 프로세스나 스레드가 있다면 그 프로세스는 언젠가 임계구역에 들어올 수 있어야한다.(무한정 대기해서는 안 된다.)
임계구역 문제 해결방법
소프트웨어적 해결법
뮤텍스,세마포어, 모니터 와 같은 락을 이용하여 해결 할 수 있다.
하드웨어적 해결법
Test-and-Set : 하나의 원자적 연산으로 변수의 값을 테스트하고 설정합여 상호배제를 구현할 수 있다.
뮤텍스 락
상호 배제를 위한 동기화 기술 중 하나로 뮤텍스는 공유 리소스에 한 번에 하나의 스레드만 접근할 수 있도록 보장해주는 메커니즘이다. 뮤텍스 락을 사용하면 여러 스레드가 동시에 같은 데이터나 리소스를 수정하는 것을 방지할 수 있다.
뮤텍스의 특징
- 상호 배제 : 한 번에 하나의 스레드만 락을 소유할 수 있다.
- 데드락 방지
Synchronized 를 이용한 뮤텍스 구현
public class MutexExample {
private final Object lock = new Object();
public void printNumbers() {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
MutexExample example = new MutexExample();
Thread t1 = new Thread(() -> {
example.printNumbers();
});
Thread t2 = new Thread(() -> {
example.printNumbers();
});
t1.start();
t2.start();
}
}
ReentrantLock을 이용한 뮤텍스 구현
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
private final Lock lock = new ReentrantLock();
public void printNumbers() {
lock.lock(); // 락 획득
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
System.out.println();
} finally {
lock.unlock(); // 락 해제
}
}
public static void main(String[] args) {
MutexExample example = new MutexExample();
Thread t1 = new Thread(() -> {
example.printNumbers();
});
Thread t2 = new Thread(() -> {
example.printNumbers();
});
t1.start();
t2.start();
}
}
세마포어
뮤텍스와 같이 동기화 메커니즘이다.
초기 카운트 값에 따라 스레드나 프로세스가 동시에 임계 구역에 접근할 수 있다.
간단히 설명하면 뮤텍스는 임계구역에 하나씩만 진입할 수 있는 반면 세마포어는 둘 이상이 임계 구역에 동시 진입 할 수 있게 하였다.
세마포어 특징
- 카운터 기반의 동기화 : 세마포어는 내부적으로 정수 카운터를 유지하여 접근하는 수를 제어합니다.
- P/V연산 P연산을 통해 카운터를 감소시키고 V연산은 카운터를 증가시킵니다.
- 카운팅 세마포어와 이진 세마포어로 나눠지며 이진 세마포어는 0또는 1을 가진 뮤텍스와 비슷한 동기화 바익이다
java를 이용한 세마포어 구현
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
Thread thread1 = new Thread(new Task(semaphore, "Thread-1"));
Thread thread2 = new Thread(new Task(semaphore, "Thread-2"));
thread1.start();
thread2.start();
}
}
class Task implements Runnable {
private final Semaphore semaphore;
private final String name;
public Task(Semaphore semaphore, String name) {
this.semaphore = semaphore;
this.name = name;
}
@Override
public void run() {
try {
// P 연산 (acquire)
semaphore.acquire();
System.out.println(name + " acquired the semaphore.");
// 임계구역
System.out.println(name + " is doing work...");
// V 연산 (release)
semaphore.release();
System.out.println(name + " released the semaphore.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
모니터
세마포어는 P와V연산을 잘못하면 예기치 못한 결과를 얻을 수 있는데 모니터는 사용자가 사용하기에 편리한 도구이다.
모니터는 공유자우너과 공유자원에 접근하기 위한 인터페이스를 묶어 관리한다. 기리고 반드시 인터페이스를 통해서만 공유 자원에 접근하도록 한다.