멀티스레드 환경에서 공유자원의 접근을 위해서는 Lock을 사용한다. 스레드는 Lock을 얻기 위해서 여러가지 경쟁 방식을 사용할 수 있다. Lock을 얻을 때까지 계속 대기하는 방식, 랜덤한 시간을 기다렸다가 다시 시도하는 방식, 제 3자가 분배하는 방식이 있다.
Spin Lock
스핀락은 Lock을 얻을 때까지 루프를 돌며 대기하는 방식이다. 제일 간단하지만, 계속 연산을 실행하므로 자원을 낭비한다고 할 수 있다. Interlocked의 CompareExchange는 컴파일러에서 여러번의 작업을 나누어 하는 것이 아닌 한번의 동작처럼 작동하므로 멀티스레드 환경에서 데이터 일관성이 깨지지 않기 때문에 사용하였다.
using System;
namespace Server {
class SpinLock {
volatile int _locked = 0;
public void Aquire() {
while (true) {
int expected = 0;
int desire = 1;
// CompareExchange는 _locked의 값과 expected의 값을 비교하고,
// 값이 같다면 desire의 값을 넣어주고 바뀌기 전 값을 반환한다
if (Interlocked.CompareExchange(ref _locked, desire, expected) == expected)
break;
}
}
public void Release() {
_lock = 0;
}
}
class Program {
static void Main(string[] args) {
static Spinlock _lock = new Spinlock();
_lock.Aquire();
// 공유 자원 작업
_lock.Release();
}
}
}
Yield
다음은 랜덤한 시간 또는 정해진 시간 대기 후 다시 lock 얻는 것을 시도한다. Thread의 Sleep과 Yield를 사용하여 구현한다. 대기하는 동안 다른 작업을 실행하여 무한정 대기하는 것보다 효율이 좋다. 그러나 대기하는 시간이 짧아서 lock을 확인하는 빈도가 잦아지면 Context Switching이 자주 발생하면서 성능이 떨어질 수 있다.
using System;
namespace Server {
class Program {
volatile int _locked = 0;
static void Main(string[] args) {
while (true) {
int expected = 0;
int desire = 1;
if (Interlocked(ref _locked, desire, expected) == expected)
break;
Thread.Sleep(1); // 1ms 동안 대기
Thread.Sleep(0); // 우선 순위가 더 높은 스레드에게 양보
Thread.Yield(); // 작업이 있는 모든 스레드에게 양보
}
}
}
}
AutoResetEvent
AutoResetEvent는 제 3자가 스레드의 작업 순서를 정해주는 방법이다. 다른 스레드의 작업이 끝나고 새로 작업을 시작할 수 있으면 예약을 걸어놓은 스레드에게 이벤트로 알려주는 것이다. 사실 기존의 Lock이 잠겨있는지 확인하려면 커널영역까지 접근하여 확인을 해야하는데 이 과정이 매우 느리다. 따라서 다른 작업을 하고 있다가 제 3자에게 이벤트를 받는 방식이 효율적이라고 할 수 있다.
using System;
namespace Server {
class Program {
AutoResetEvent _AREvent = new AutoResetEvent(true);
// 생성자에서 false면 잠긴상태, true면 열린상태
public void Aquire() {
_AREvent.Set();
// Lock을 열린 상태로 만들어준다.
}
public void Release() {
_AREvent.WaitOne();
// Lock을 닫힌 상태로 만들어준다.
// WaitOne안에는 Reset 메소드도 포함되어 있다. Reset 메소드가 닫힌 상태로 만든다.
}
static void Main(string[] args) {
Aquire();
// 작업
Release();
}
}
}
AutoResetLock 말고도 ManualResetLock이 존재하는데, 이는 WaitOne 함수에 Reset 메소드가 들어있지 않아서 직접 Reset 함수를 호출하여 Lock을 닫아주어야 한다. 한 번에 하나의 스레드가 아닌 한 번에 여러 개의 스레드가 작업을 할 때 이용한다.
'OS' 카테고리의 다른 글
OS - Read Write Lock / Reader Writer Lock (1) | 2024.07.23 |
---|---|
OS - Context Switching (문맥 교환) (1) | 2024.07.16 |
OS - Lock, InterLocked - C# (0) | 2024.07.15 |
OS - 메모리 배리어 - Memory Barrier (0) | 2024.07.11 |
OS - 캐시(Cache) (0) | 2024.07.09 |