멀티 쓰레드
문맥 교환 (=컨텍스트 스위칭(Context Switching))
우리는 이전 시간에 아래와 같은 코드에서 while문을 이용해 반복적으로 실행되는 것을 스핀락이라고 학습하였다. 그렇지만 문맥 교환 방법(PPT 예시 - 랜덤 메타 방식)으로 이를 구현하려고 하면 어떻게 해야할까? 방법은 간단하다. 아래 소스코드에서 일정 시간을 쉬다가 오면 이것이 문맥 교환 방법이 되는 것이다.
public void Acquire()
{
while (true)
{
int expected = 0;
int desired = 1;
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
}
}
쉬는 방법에는 총 세 가지의 방법이 존재한다.
public void Acquire()
{
while (true)
{
int expected = 0;
int desired = 1;
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
// 쉬다 올게~
// Thread.Sleep(1); // 무조건 휴식 => 무조건 1ms 정도 쉬고 싶어요
// Thread.Sleep(0); // 조건부 양보 => 나보다 우선순위가 낮은 애들한테는 양보 불가 => 우선순위가 나보다 같거나 높은 쓰레드가 없으면 다시 본인에게
// Thread.Yield(); // 관대한 양보 => 관대하게 양보할테니, 지금 실행이 가능한 쓰레드가 있으면 실행하세요. => 실행 가능한 애가 없으면 남은 시간을 본인에게 소진한다.
}
}
Thread.Sleep() 메서드는 양수와 0을 넣는 두 가지 방법이 존재하는데, 값에 따라 로직의 동작 방식이 완전히 달라지기 때문에 Sleep(1), (0), Yield()와 같이 세 가지로 나누어 볼 수 있다.
Sleep(양수)은 양수 시간만큼의 휴식을 의미한다. 만약 1을 넣을 경우 1ms 쉬게 되는 것이다. 하지만 이는 희망사항이고 실제로는 운영체제(OS)가 스케줄러를 통해 이를 관리한다.
Sleep(0)은 조건부 양보와 같은 의미를 가진다. 나보다 우선 순위가 낮은 애들한테는 양보가 불가능하며, 우선순위가 나보다 같거나 높은 쓰레드가 없으면 다시 본인에게 돌아온다. 그렇다면 우선 순위는 어떻게 구분지어야 할까? 우리는 이전 시간에 식당에서 직원 얘기를 하였을 때 직원의 중요도에 따라 얘기하며 우선순위를 나누었다. 예를 들어 식당 총괄 매니저와 같이 중요한 직원은 업무 필요에 있어서 우선순위가 높게 측정되는 것과 유사한 개념이다. 물론 상대적으로 우선 순위가 낮은 직원들은 기아현상*을 갖게 된다.
* 기아 현상 = 어떤한 우선 순위로 작업을 처리할 때 우선순위가 낮은 작업은 영원히 처리되지 않는 문제. ex) 우선순위가 3인 작업이 있는데, 계속해서 우선순위가 4이상인 작업이 새롭게 들어온다면, 우선순위가 3인 작업은 오랫동안 기다렸는데도 불구하고 영영 처리되지 않는다.
출처 : https://itwiki.kr/w/%EA%B8%B0%EC%95%84_%ED%98%84%EC%83%81
Yield는 관대한 양보이다. 지금 실행이 가능한 쓰레드가 있으면 실행하도록 양보하는 것이다. 그렇지만 실행 가능한 애가 없을 경우 남은 시간을 본인에게 사용한다. 그렇다면 세 가지 쉬다 오는 방법중 어떤 것이 가장 좋을까? 어떤 것이 가장 좋다고 확실하게 얘기할 수는 없다. 왜냐하면 사용자의 목적, 프로그램 동작 환경에 따라 필요한 것이 달라지기 때문에 본인에게 가장 필요한 기능을 사용하는 것이 가장 좋다.
우리는 테스트를 위해서 Thread.Yield()를 사용한다. 문맥교환의 장점은 무한정으로 돌며 작업을 실행하는 것을 예방할 수 있다는 것이다. 지금 우리는 비교적 간단한 작업을 하고 있기에 문제가 없지만, 보다 무거운 작업을 실행하는 와중에 작업을 계속 기다린다면 이는 문제가 생길 수 있다.
public void Acquire()
{
while (true)
{
int expected = 0;
int desired = 1;
if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
break;
Thread.Yield();
}
}
예시로 알아보는 컨텍스트 스위칭의 장/단점
고급 식당 예제로 다시 돌아가보자. 우리는 일전에 영혼을 통해 각 식당의 직원을 빙의하였다. 사실 여기서 언급은 하지 않았지만 식당 관리자 또한 빙의 대상에 포함된다. 왜냐하면 식당 관리자를 통해 어떤 직원을 다음으로 선택할지 정해야 하기 때문이다. 결국 관리자도 직원으로 생각하면 된다.
우리가 한식 식당에서 일식 식당의 직원으로 빙의해본다고 생각해보자. 우리가 생각했을 때에는 한식 식당 직원에서 일식 식당 직원으로 뿅~! 옮기면 되겠지? 생각하지만 사실은 한식 식당 직원 → 식당 관리자 → 일식 식당 직원으로 처리가 진행된다. 또한 한식, 일식, 패밀리 레스토랑과 같은 식당들을 일반적인 프로그램(메모장, 계산기 등)이며, 식당 관리자와 영혼은 커널 모드이며 운영체제의 핵심이며, 중요한 기능이다.
우리는 이전 시간에 학습한 복습한다면 관리자는 CPU의 코어이며, 코어 안에는 ALU와 캐시 장치가 있다고 했다. 레지스터는 이전 시간에 임시로 메모하기 위한 공간이라고 하였지만 사실 레지스터는 하나만 있는 것이 아닌 여러개 있으며 각각의 용도가 다르다. 연산을 위한 용도, 무엇을 실행하고 있는지 확인하기 위한 용도, 메모리 접근을 위한 용도 등이 있다.
레지스터가 중요한 이유는 드라마에서 흔히 사용되는 등장 인물 A에게 빙의한 상태가 되고, A가 일전에는 하지 않던 이상 행동을 하는 것과 같다. 즉. 빙의가 끝나면 그 동안 했던 일들을 잊고, 기억을 하지 못하는 것과 같은 것이다. 실제로 우리 예제에서도 이와 같은 개념이다. 즉 직원은 껍데기이며 모든 기억들은 직원에게 종속적인 것이 아니다.
직원과 식당의 대한 정보는 RAM에 저장된다. 이는 코어가 직원을 빙의할 때에는 반드시 해야하는 작업이 있는데 이 것이 바로 컨텍스트이다. 이는 모든 정보를 복원하는 작업이다. 우리가 영혼을 이용해 직원을 빙의하겠다고 하면 메모리의 있는 정보를 이용해 빙의하려는 직원의 모든 정보를 뽑아와야 한다는 것이다. 직원이 무슨 상태였는지, 무엇을 하고 있었는지, 식당은 어떤 형태인지 등 다양한 정보를 가져와야 한다는 것이다.
추가적으로 식당의 지리적은 구조 또한 가상 메모리와 연관되어 있다. 만약 우리가 빙의한 직원이 이직한 것이 아니고 같은 식당에서 일을 하고 있으면 가상 메모리를 변경하지 않아도 되는데, 직원 A가 한식 식당에서 일식 식당으로 이직했으면 식당 구조가 달라졌으므로 가상 테이블 또한 변경해주어야 한다.
즉. 이렇게 정보를 저장, 복원하는 단계가 부담이 발생하는 것이다. 따라서 우리가 단순히 이전 프로그램에서 Sleep, Yield를 하며 소유권을 포기하고 남에게 주는 행동이 항상 좋은 행동은 아니라는 것이다. 이러한 식으로 영혼을 바꿀 때마다 어마어마한 비용 소모가 있기 때문에 경우에 따라서는 스핀 락과 같이 유저 모드에서 대기하는 것이 효율적일 수 있다는 것이다.
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 4-2-13. 멀티쓰레드 프로그래밍 : ReaderWriterLock (0) | 2023.09.16 |
---|---|
Part 4-2-12. 멀티쓰레드 프로그래밍 : 이벤트(AutoResetEvent, ManualResetEvent), 뮤텍스 (0) | 2023.09.16 |
Part 4-2-10. 멀티쓰레드 프로그래밍 : SpinLock (0) | 2023.09.13 |
Part 4-2-9. 멀티쓰레드 프로그래밍 : Lock 구현 이론 (0) | 2023.09.13 |
Part 4-2-8. 멀티쓰레드 프로그래밍 : 데드락 (0) | 2023.09.12 |