동기화의 의미
프로세스 동기화란 프로세스들 사이의 수행 시기를 맞추는 것을 의미합니다.
실행 흐름을 갖는 모든 것(프로세스, 스레드 등)은 동기화의 대상입니다.
- 실행 순서 제어 : 프로세스를 올바른 순서대로 실행하기
- 상호 배제 : 동시에 접근해서는 안되는 자원에 하나의 프로세스만 접근 허용
- 예시 : 은행 계좌 문제, 생산자와 소비자 문제
공유 자원과 임계 구역
공유 자원이란 은행 계좌의 잔액같이 프로세스들이 공동으로 사용하는 자원으로 전역변수, 파일, 입출력장치, 보조기억장치도 공유 자원이 될 수 있습니다. 그리고 공유 자원에 접근하는 코드 중 동시에 실행하면 문제가 발생하는 코드 영역을 임계 구역이라고 합니다.
레이스 컨디션
- 잘못된 실행으로 인해 여러 프로세스가 동시 다발적으로 임계 구역의 코드를 실행하여 문제가 발생하는 경우
- 고급 언어가 실행 과정에서 저급언어로 변환되어 실행되는 과정 중 문맥 교환이 발생되어 해당 문제가 나타남
임계 구역문제 방지를 위한 세 가지 원칙
- 상호배제(mutual exclusion) : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없다.
- 진행(progress) : 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 한다.
- 유한 대기(bounded waiting) : 한 프로세스가 임계구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 한다.
동기화 기법
뮤텍스 락(Mutex Lock)
상호 배제를 위한 동기화 도구로 하나의 프로세스가 임계 구역에 진입했을 때 다른 프로세스가 진입하는 것을 막기 위한 '자물쇠' 역할을 합니다. 하나의 공유 자원에 접근하는 프로세스를 상정한 방식입니다.
구현
- 전역 변수 lock : 자물쇠 역할
- acquire() : 잠금
- release() : 잠금 해제
세마포(semaphore)
공유 자원이 여러 개 있을 경우 각각의 공유 자원에 프로세스가 접근이 가능하게 하는 동기화 도구입니다. 세마포를 이용하면 동시에 실행되는 프로세스 혹은 스레드 간에 상호 배제를 위한 동기화 뿐만 아니라 실행 순서 제어를 위한 동기화도 할수 있습니다.
구현
- 전역 변수 S : 임계 구역에 진입할수 있는 프로세스의 개수( 사용 가능한 공유자원의 개수)
- wait() : 임계 구역 출입 가능 여부를 알려주는 함수
- signal() : 대기중인 프로세스에게 입장 허가 신호를 주는 함수
뮤텍스 락과 세마포의 단점
- 빠쁜 대기 방식 : 반복적으로 진입 가능 여부 확인
> 세마포는 대기 큐를 사용하여 CPU 주기 낭비를 줄이는 방식을 사용합니다.
모니터(monitor)
세마포는 wait와 signal 함수 명시에 있어 번거로움이 있습니다. 또한잘못된 코드 기입 시 문제가 발생 할 수 있습니다. 이러한 단점을 극복하기 위해 등장한 동기화 도구가 모니터입니다. 모니터는 공유 자원과 공유 자원에 접근하기 위한 인터페이스를 묶어 관리하는 동기화 도구입니다. 모니터를 통해 프로세스는 반드시 인터페이스(통로)를 통해서만 공유 자원에 접근하도록 합니다.
스핀락(Spinlock)
임계 구역에 진입이 불가능할 때 진입이 가능할 때까지 루프를 돌면서 재시도하는 방식의 동기화 도구입니다.
운영 체제의 스케줄링 지원을 받지 않기 때문에, 해당 스레드에 대한 문맥 교환이 일어나지 않습니다. 그러나 진입 대기 시간이 길어질 수록 cpu의 점유율이 높아져 효율이 떨어지게 됩니다.
스핀락 구현
class SpinLock
{
// true : 사용중 false : 비었음
// volatile 가시성을 보장
volatile bool _locked = false;
public void Acquire()
{
// 잠금이 풀릴 때까지 루프
while(_locked)
{
// 잠김이 풀리기를 기다린다.
}
// 이제 내 차례
_locked = true;
}
// 사용 완료
public void Release()
{
// 반납
_locked = false;
}
}
class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for(int =0; i < 100000; i++)
{
_lock.Acquire();
-num++;
_lock.Release();
}
}
static void Thread_1()
{
for(int =0; i < 100000; i++)
{
_lock.Acquire();
-num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1,t2);
Console.WriteLine(_num); // 최종적으로 _num의 값이 0인지 아닌지 확인
}
}
결과값
-8914
결과값이 0이 아닌 이유
임계 구역을 화장실로 비교해봤을때 두 사람(쓰레드)가 있으면 서로 먼저 들어가려고 경합을 버리게 됩니다. 이때 드물게 화장실에 사람이 없을 때 두사람이 동시에 들어가는 경우가 생길 수 있는데 이 때 두 사람이 들어간 상태에서 문을 잠궈버리게 됩니다.
해결 방법
1. 화장실에 들어가고 2. 문을 잠구는 행동을 서로 나눠서 행하는 것이 아니라 화장실에 들어간 다음에 문을 잡그는 것이 하나의 행동으로 이루어지게 하면 됩니다. 다시말해 동시에 들어가는 상황을 차단하면 됩니다. 이는 Interlocked 클래스를 사용하여 원자 단위 연산을 통한 지정 값을 받아오면 됩니다.
Before
class SpinLock
{
// true : 사용중 false : 비었음
// volatile 가시성을 보장
volatile bool _locked = false;
public void Acquire()
{
// 잠금이 풀릴 때까지 루프
while(_locked)
{
// 잠김이 풀리기를 기다린다.
}
// 이제 내 차례
_locked = true;
}
// 사용 완료
public void Release()
{
// 반납
_locked = false;
}
}
After1 ) Test-And-Set
class SpinLock
{
// 사용중 : 1 비었음 : 0
// volatile 가시성을 보장
volatile int _locked = 0;
public void Acquire()
{
while(ture)
{
// Interlocked.Exchange(ref _locked, 1); : _locked을 1로 바꾸고
// _locked의 초기값을 반환
// 값에 bool값 사용 불가
int original = Interlocked.Exchange(ref _locked, 1);
if(original == 0)
break;
}
}
// 사용 완료
public void Release()
{
// 반납
_locked = 0;
}
}
After2 ) CAS : Compare-And-Swap
class SpinLock
{
// 사용중 : 1 비었음 : 0
// volatile 가시성을 보장
volatile int _locked = 0;
public void Acquire()
{
while(ture)
{
// CAS Compare-And-Swap
int expected = 0; // 비교대상 값
int desired = 1; // 원하는 값
if(Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
// 사용 완료
public void Release()
{
// 반납
_locked = 0;
}
}
'OS' 카테고리의 다른 글
가상 메모리 (0) | 2024.01.20 |
---|---|
데드락(Deadlock, 교착 상태) (0) | 2024.01.20 |
CPU 스케줄링 (2) | 2024.01.14 |
1. 운영체제(OS, Operating System) (0) | 2024.01.07 |
프로세스와 스레드 (0) | 2023.12.15 |