반응형
락 구현 개념
- 화장실에 누군가가 문을 잠근 채로 있다면?
- 그냥 기다린다.
- 그런데 오랜 시간이 지나도 나오지 않는다면?
- 조금 시간이 지난 뒤에 다시 온다.
- 보장이 안되고 랜덤성이 강함
- 다른 사람이 먼저 채갈수도
- 조금 시간이 지난 뒤에 다시 온다.
- 줄서기 알바를 쓴다
- 줄서고있는 알바가 화가 난다
컴퓨터에서의 개념
- Spin Lock
- 계속 뺑뺑이를 돌면서 대기
- 계속 실행 상태로 있기 때문에 자원을 차지하고 있음
- CPU 점유율이 튄다
- Context Switching
- 쓰레드가 소유권을 포기
- 코어가 다른 쓰레드에게 가버린다
- 일정 시간이 지난 후 다시 그 쓰레드로 복귀
- 왔다갔다 하므로 오버헤드 부담이 있음
- Auto Reset Event
- 이벤트를 통해 통보를 받음
- 커널에다가 이벤트 발생을 명령
- 깨어나는 시점이 다른 것 보단 정확함
SpinLock 구현
//락이 풀릴 때까지 대기
class SpinLock
{
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 i=0; i<10000000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 10000000; 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);
}
}
- Release의 경우 그냥 나 혼자 문 여는 작업일 뿐이므로 저거 한 줄로 충분하다!
- 이상한 값이 나온다!
- 뭐가 문제일까
- 자물쇠가 잠기기 전에 두 쓰레드가 모두 입장해 버림

- 해결하려면, 들어가고 잠그는 동작이 모두 하나의 동작으로 이뤄져야 한다!
- 아래처럼 해결해보자
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0) break;
}
}
- Interlocked.Exchange()
- 첫 파라미터를 반환값으로 가짐
- 두번째 파라미터와 첫 파라미터를 교환
- 이 과정을 통해 스핀 락 구현이 가능
- 반환값으로 0이 나왔다면, 풀려 있는 상태에서 내가 잠궜다는 것이 확실해 지므로 break;
- 1이 나왔다면, 이미 잠겨있다는 뜻이므로 다시 반복

- 바뀐 함수를 싱글쓰레드 개념으로 표현하자면
int original = _locked;
_locked = 1;
if(original == 0)
break;
- 이런 동작이 하나의 단위로 수행됐다고 보면 된다.
- 하지만 그렇게 직관적으로 보이진 않는 것 같다
- if(_locked == 0) _locked = 1; 이게 더 직관적인 듯 하다.
while (true)
{
int expected = 0;
int desired = 1;
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
//첫, 세번째 인자 비교
//같으면 첫, 두번째 인자 교환
//리턴값은 첫 인자의 original 값
}
- 즉, locked가 내가 원하던 값이었다면 break시키도록 조금 더 가독성을 강화
Context Switching
- 락을 얻지 못하면, 다른 일을 하다가 일정 시간 후 다시 돌아온다
- 휴식 방법
- Thread.Sleep(1);
- 무조건 휴식, 1ms
- Thread.Sleep(0);
- 조건부 양보 => 나보다 우선순위가 낮은 쓰레드에는 양보 불가
- Thread.Yield();
- 관대한 양보 => 조건 없이 관대하게 양보, 다른 쓰레드가 있으면 그거 하세요 => 없으면 그냥 내가 쓴다
- 누가 더 좋다기보단, 각 상황에 맞춰 맞는 것을 쓰자
- Thread.Sleep(1);
- 무한정 뺑뺑이를 예방하는 효과가 있음. 장점만 존재할까?
- 그러나 쓰레드 교체 과정에서 오버헤드가 분명히 발생함
- User mode -> Kernel mode -> User mode 의 전환 과정
- 그렇다면 직원(쓰레드)의 정보는 어떻게 가지고 있나?
- 메모리 어딘가에 저장되어 있지, 쓰레드에 무언가 저장돼있지 않다.
- 그러므로 쓰레드 교체하는 과정에서 이 정보들을 저장하고, 로드하는 과정이 존재하는 것
- 어떤 상태인지, 코드를 어디까지 실행했는지 등
- 그러므로 오버헤드가 크다!
- 소유권을 포기 하는 것이 좋은 일 만이 아니다.
AutoResetEvent
- 다른 직원에게, 화장실이 비었는지 알려달라고 부탁하는 방식
- 해당 쓰레드 입장에서는, 자물쇠가 풀릴때만 들어가면 된다는 장점이 있음
class Lock
{
//bool <- 커널이 조정
AutoResetEvent _available = new AutoResetEvent(true);
//문을 연 상태로 시작할지, 닫은 채로 시작할 지 인자로 결정
//true가 열려 있는 것, 열고 들어가면 자동을 닫힘
public void Acquire()
{
_available.WaitOne(); // 입장 시도
//이거 한줄로 기다리고 들어가고 잠그고 다 해줌
//_available.Reset(); //잠그는것, 자동으로 해주기 떄문에 안써도 됨
}
public void Release()
{
_available.Set(); //flag = true
//자물쇠를 푸는 것
}
}
- SpinLock 처럼 100만번 수행하면, 너무 오래걸려서 끝나지 않는 것 처럼 보임
- 10000번 정도 반복이라면 금방 끝남
- 즉, 편해지고 정확해지지만, 커널에 왔다갔다 하면서 속도가 아주 느려진다
- Event는 락이 아니라도 다른데에 쓰기도 함
ManualResetEvent
class Lock
{
//bool <- 커널이 조정
ManualResetEvent _available = new ManualResetEvent(true);
public void Acquire()
{
_available.WaitOne(); // 입장 시도
_available.Reset(); //입장 후 문 닫기
//Manual에서는 이 작업을 수동으로 해야 함
}
public void Release()
{
_available.Set(); //flag = true
//자물쇠를 푸는 것
}
}
- Auto와 다르게, 자물쇠를 잠그는 동작이 자동이 아니라 나뉘어 있다.
- 그렇기 때문에, 잠그기 전에 두 쓰레드가 동시에 입장이 가능해진다!
- Auto를 써서 한 동작으로 끝내자
- 그렇다면 Manual은 언제 쓰냐?
- 꼭 한번에 한 쓰레드만 입장할 필요가 없는 경우
- 로딩이나 패킷 받는 오래걸리는 작업을 기다리고, 그게 끝나면 모든 쓰레드가 재가동 되게 하는 경우
- 꼭 한번에 한 쓰레드만 입장할 필요가 없는 경우
- 아무튼 이전과 다르게 Kernel이 코드에 개입한다는 개념이 매우 중요!
- 부담이 많이 된다는 것도!
Mutex
static Mutex _lock = new Mutex();
static void Thread_1()
{
for(int i=0; i<1000000; i++)
{
_lock.WaitOne(); //문 열고 들어가서 잠그고
_num++;
_lock.ReleaseMutex(); //열어줌
}
}
static void Thread_2()
{
for (int i = 0; i < 1000000; i++)
{
_lock.WaitOne();
_num--;
_lock.ReleaseMutex();
}
}
- 커널 동기화 객체의 개념
- 관리자(커널)가 직원들(쓰레드)의 우선순위를 잡아줌
- AutoResetEvent랑 뭐가 다르냐?
- Mutex가 정보를 더 가지고 있음
- 한 쓰레드가 몇 번이나 잠갔는지 카운트
- Thread ID도 갖고 있음 -> Lock, Release가 같은 쓰레드가 한 것임을 확인
- 그렇기 때문에 너무 비싼 명령임
- 별로 쓸 일 없다
- Mutex가 정보를 더 가지고 있음
반응형
'C# > 네트워크' 카테고리의 다른 글
[c#][서버] Thread Local Storage (0) | 2024.06.03 |
---|---|
[c#][서버] ReaderWriterLock (0) | 2024.06.03 |
[c#][서버] 데드락 (0) | 2024.06.02 |
[C#][서버] Lock 기초 (0) | 2024.06.02 |
[C#][서버] Interlocked (0) | 2024.06.01 |