Context Switching

CJB_ny·2022년 2월 14일
0

Unity_Server

목록 보기
14/55
post-thumbnail

지난 시간에 우리는 Spinlock에 대해서 알아 보았고

SpinLock이라는게 결코 어려운 개념이 아니였다.

InterLock.CompareExchange에서

lock을 획득하려는 시도를 하고 있었고

break에 왔다는 것은 굉장히 해피한 상황이라는 것이다.

만약 이것이 실패를 한다면 다시

while문으로 올라가서 성공을 할 때까지 시도를 하는 방법이라고 했다(존버 메타)

그래서 이제는 두번째 방법에 대해서 알아볼것이데

소위 우리가 PPT에서 보았던

1. 랜덤 메타

"랜덤 메타"이다.

화장실에 누군가가 있다면은 무작정 기다리는 게 아니라,

자리로 돌아와서 랜덤하게 다시 가보아서 있으면 들어가거 없다면 다시 자리로 돌아오고 그런 느낌!

사실 이것을 구현을 하면

거의 이것과 비슷하게 구현이 되긴 한다.

그래서

이부분 자체가 SpinLock이라고는 볼 수는 없고

이녀석을 시도를 하고, 만약 실패했을 때, 이제 어떻게 할 것인가가

이제 Lock을 가르는 여러가지 방법이 될 텐데

만약, 실패를 하자마자

while 루프를 시작해가지고

이렇게 재차 시도를 하는 것은 SpinLock이 될 것이고

그게 아니라,

이 밑에서 쉬다가 오면 아까 말한 두번째 방법 = 랜덤 메타가 될 것이다.

그래서 결국 여기서 쉬다가 오는 것인데

방법은 사실 여러가지가 있다.

그래서 이렇게

  • Sleep을 하는 경우

    • 양수를 넣는 경우
    • 0을 넣는 경우
  • Yield를 하는 경우

이렇게 두가지가 있는데

            Thread.Sleep(1);

            Thread.Sleep(0);

이것이 행동하는 방식이 완전히 달라지기 때문에

사실상 세가지 방법이 있다고 생각을 하면된다.

그런데 얘내들이 각각의 미묘한 뉘앙스 차이가 있기는 하니까

이것을 설명을 해주도록 하겠다.

Sleep을 보면은 인자로 millseconds를 받는데 쓰레드를 인자로 받은 시간만큼 대기를 하겠다라는 말이다.

그래서 1ms만큼 무조건 쉬고싶어요 인데

1ms쉬는것은 어디까지나 내가 원하는 "희망 사항"이고

실제로 몇초를 쉴지는 운영체제가 결정을 하게된다.

운영체제에서 "scheduler" 라고 누가 몇초동안 쉬게 할것인지를 관리를 하는

그런 관리자가 있었는데 그녀석이 결정을 하기는 하는데

최대한 우리가 요청한 대로 할려고 해주긴 할 것이다.

그다음에 알아 볼 것은

            Thread.Sleep(0);

은 어떤 의미이냐 하면은

양보이긴 양보인데 조건부 양보이다.

조건부 양보 => 나보다 우선순위가 낮은 애들한테는 양보 불가 => 나보다 우선순위가 같거나 높은애들이 없으면 다시 본인한테 옴.

그런데 여기서 말하는

"우선순위" 가 무엇이냐 하면은

이전에 쓰레드에서 대해서 얘기를 할 때(식당에서 직원의 예를 들었을 때)

직원마다 모두 공평하게 우선순위가 같을 수도 있지만

아닐 수도 있다고 했었다.

경우에 따라 특정 직원(쓰레드가) 더 중요하다고 판단이 되어서

우선순위를 높여야 된다고 하면은

나중에 설정을 할때 조금 우선순위를 높여서 실행되게끔 만들 수 있다.

그러면 "상대적"으로 우선순위가 낮은 쓰레드들은 "기아 현상"을 겪게 될 것이다.

Yiled는

관대한 양보 => (조건을 안걸고) 관대하게 양보할테니, 지금 실행가능 한 쓰레드가 있으면 그 녀석을 실행해주세요.

이런 의미이다.

그런데 이녀석도

=> 실행 가능한 애가 없으면 남은시간 자신에게 소진을 하기는 한다.

그래서 이렇게

세가지 버젼이 있는데

세가지중에 하나 골라서 사용을 하면 될 것이다.

여기서 어떤게 좋다는 것은 굉장히 애매하고

그냥 우리가 만드는 프로그램에 따라서 테스트를 해보는것이 더 좋을 수 있다.

그래서 실행을 해보면

이렇게 별다른 차이를 느끼지는 못하겠지만

똑같이 동작하는 것을 볼 수 있다.


그래서 아무튼 이렇게 해주면

"장점"이 무어냐 하면은

이전처럼

무한정 대기를 하는(뺑뺑이를 도는) 상황은 예방을 해줄 수 있을 것이다.

만약

이 안에서 num++같은 간단한 작업을 하는게 아니라

굉장히 무거운 작업을 하고 있었다라고 하면은

경우에 따라 이녀석을 계속 대기를 시키면서 실행하는 것은 굉장히 부담이 되는 작업 일 수 있기 때문에

            Thread.Yield(); 

로 한번씩 쉬다오는것도 경우에따라 괜찮은 방법이 될 수 있다.

2. Context Switching

자, 이렇게 해서 Sleep이나 Yiled와 같은 녀석들을 사용을 해서

자신이 보장을 받은 시간을 포기를 하고 다른 쓰레드들이 실행될 수 있게 시간을 양보하는 방법에 대해서 알아 보았다.

그런데, 이 방법이 무작정 좋은지 "장점"만 있는건지 보면은

굉장히 애매한 문제이기는 하다.

그리고 가장 문제가 되는 부분은 Context Switching이라는 문제인데

이게 쉽게 말해서

"빙의"를 할 때 빙의를 하고나서 다른 직원에게 다시 빙의를 하는 그 비용을 말하는 것이다.

(그러니까 시간 할애를 1번쓰레드 에게 했다가 다시 CPU코어가 다른 2번쓰레드에게 가서 시간 할애를 할때 드는 왔다 갔다 거리는 비용)

그래서 이런식으로 빙의를 한다고 했엇는데 식당관리자가

사실은

이런식으로 관리자도 있기 때문에 식당 관리자도 빙의 대상이기는 하다.

왜냐하면은, 나중에는 여기있는 "관리자"가 어떤 직원을 다음으로 선택을 할지 골라준다고 했었다.

그런데 프로그램으로 치면은

식당관리자 영역도 CPU가 실행을 시켜야 하는 코드이기 때문에

결국에는 어떻게 보면은 관리자도 일종의 "직원"(쓰레드)라고 보면 된다.

그래서 이런식으로 관리자에게 영혼이 빙의가 되는 상태가 되면은

이제는 약간 관리자 모드로 조금더 중요한 행동들을

즉, 어떤 직원을 실행시킬지와 같은 것들을 하게 될 것이다.

이것을 우리 컴퓨터로 치면은

Windows Kernel의 역할을 이 "관리자"가 하는 것이라고 보면 된다.

그래서 지금

만약 왼쪽 직원에 빙의를 하다가 오른쪽 직원에 빙의를 한다고 가정을 하면은

그림상으로 보면은

그냥 옆으로 뿅! 하고 넘어 가면 될거 같지만

그렇게 할수는 없고

반드시 관리자모드를 한번 거쳤다가

오른쪽 직원에게 빙의를 해야한다.

벌써 생각보다 단계가 늘어난 것을 볼 수있다.

그래서 지금

여기서 위에 있는 부분들(파란 선으로 구분되어져서 위에있는 부분들)을

UserMode라고 하는 것이고 ( 일반적인 실행프로그램, 메모장, 그림판, 게임이나 게임서버도 UserMode에 있는 것이다 )

파란선 기준으로 아래에 있는게

KernelMode라고 해가지고

운영체제에 가장 핵심적이고 비밀스럽고 중요한 부분들이 있는 부분이다.

한마디로 Windows 코드가 돌아간다고 보면 된다.

즉 Windsows, Linux와 같은 운영체제 코드가 돌아가는 부분이라고 생각을 하면된다.

그 다음에 살펴 볼 것은

3. Context

그렇다면은 이제 이 직원에 관련한 정보는 과연 무엇인지가 굉장히 궁금하다.

그래서 지난시간과 지지난 시간의 내용을 압축을해서 설명을 하고있는데

우리가 말하는 관리자는 지금 CPU의 코어라고 했었다.

그리고 이 코어안에는

  • ALU = 연산
  • 캐시 = 기억

하는 장치가 두가지 있다고 아주 간략하게 배웠었다.

그런데 여기서 한가지 굉장히 중요한 사실은 여기서

"레지스터" (초록색) 이 녀석의 역할이 사실 굉장히! 중요하다.

레지스터는 그냥 임시로 메모하기위한 공간이라고 간략하게 배웠었는데

레지스터는 하나만 있는것이 아니라 굉장히 여러가지가 있다.

그리고 심지어 굉장히 다르다.

내가 "연산"하는 것을 기억하기 위한 레지스터가 있고,

그게 아니라 "내가 어디까지 무엇을 실행하고 있었는지"와 같은 실행자체를 어디까지 했었는지 추적하기위한 용도의 레지스터도 있고

그리고 메모리 주소를 접근하기 위해서 사용을 하는 레지스터도 있다.

즉, 다양한 용도로 사용이 되는데

이게 중요하냐 하면은

이렇게 직원에게 빙의를 했다고 가정읗 해보자.

그런데, 우리가 쉽게 생각해서

드라마나 애니를 보면은

귀신이(영혼이) 어떤 사람에게 빙의를 한다음에

빙의된 사람은 이상한 행동을 하기 시작한다.

그런데 빙의가 끝나가지고 영혼이 떠나가면은 원래대로 돌아오면은

그동안 했던 행동들을 싸그리 다 잊어 버릴 것이다.

(대부분 그렇다)

그런데 실제로 우리의 고급식당의 예제에서도 그런 느낌이다.

사실 이 직원이라는 녀석은

껍대기만 존재를 하는 것이지.

가지고있던 모든 기억력들은 이 직원(쓰레드)에게 종속적인 것이 아니다

그리고 이 직원과 레스토랑에 대한 정보는

사실상

CPU코어의 옆에 RAM(메모리)의 어딘가에 다 저장을 하고있다.

그렇다는 것은 "Core"가 즉, 관리자가

위에있는 직원(쓰래드)에게 빙의(시간을 넣어? 줄때)할때는

"해야하는 작업"이 있는데 그게 뭐냐 하면은

"Context" 라는 모든 정보를 다 복원 시켜줘야 한다는 것이다.

그러니까 단순히 CPU 코어에서 직원에게 빙의를 하겠다고 가정을 하면은

그때 해야되는 작업이 먼저

RAM에 있던 정보들 중에서 프로그램과 관련된 정보(직원(쓰레드)와 주방에 있는 정보)를 싸그리 다 뽑아 와야 한다는 것이다.

그리고 일부는 이제 레지스터에 복원이 되는데

지난 번에 말한대로

  • 어떤 상태였는지?
  • 무엇을 하고 있었는지? == 어디까지 코드를 실행하고 있었는지?
  • 식당 구조는 어떤지?

이런것들도 RAM이 다 레지스터에다가 "복원"을 해야 된다는 말이다.

게다가 추가적으로 이런

식당에 대한 정보, 주방 위치는 어디고 계산대는 어디고

이런 지리적인 정보들도 결국 다 복원시켜줘야하는데

4. 레지스터

이부분은 "가상 메모리" 와 관련이 있다.

그래서 만약에 운이 좋게 우리가 빙의한 직원이랑

그 이전에 빙의했던 직원이랑 "같은" 식당에서 일을 하고 있었다면은

정말 다행으로

"가상 메모리"는 안 바꾸어 주어도 되는데

그게 아니라 만약,

일식집에서 일하는 직원에게 빙의를 하다가 이제는 한식집에서 일하는 직원에게 빙의를 해야된다고 가정을 하면은

아예 "식당"구조가 달라지니까

"가상 메모리"도 바꿔치기 해가지고

결국에는 이 식당에 있는 정보도 새로 추출을 해야 된다는 말이 되겠다.

그래서 뭐 정확한 내용은 사실 크게 중요한 것은 아니고

여기서 이해를 해야 할것은

가장 중요한

"레지스터"

레지스터에는 정말 온갖 정보를 다 들고 있는데

이렇게 빙의를 해가지고 옮겨 다닐 때마다

레지스터에 있는 정보를 싸그리 날리고

다시 "복원"을 해된다!

그리고 이전에 빙의를 해서 사용하던 직원의(쓰레드의) 레지스터는

RAM(메모리)에다가 고이고이 다 저장을 해가지고

나중에 다시 그 이전에 빙의를 했던 직원을(쓰레드를) 빙의를 한다면

다시 꺼내 쓸 수 있도록 뭐 만들어 줘야 할 것이다.

그래서 이런식으로(빨간 화살표 대로)

온갖 정보를 저장하고 복원하는 단계가

가장 핵심적인 부담이 될 것입니다.

그래서

이런식으로

왼쪽 오른쪽 으로 뿅! 하고 왔다리 갔다리 할 수 있을 거 같지만

그런게 절대 아니고

굉장히 어려운 작업을 하고 있었다는게

오늘의 "결론"이 될 것이다.

그래서 Kernel모드로

이렇게 옮긴 다음에 -> 온갖 정보를 다 복원 시키고

최종적으로

다음 빙의할 직원에게(쓰래드에게) 빙의를 해서

계속 이어서 실행을 하는 그런 단계를 거치는 것이다.

그래서 결국 여기서 내릴 수 있는 결론은 무엇이냐?

우리가 단순이 이전처럼 Thread.Sleep을 하건 Yiled를 하건

소유권을 포기를 하고 남에게 준다는 것은 무조건적으로 좋다는 얘기는 아니라는 말이다.

이런식으로 영혼을 한번 바꿔치기 할때마다 어마어마한 부담이 되기 때문에

경우에 따라서는 SpinLock처럼 UserMode에서

여기 위에 부분에서

계속 돌면서 뭔가를 try하는게 뭔가더 효율적일 수 도 있다는 얘기가 되는 것이다.

-끝-

profile
https://cjbworld.tistory.com/ <- 이사중

0개의 댓글