일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Private Bytes
- 프로그래밍 기초
- Toon Shader
- 벡터
- 작업 집합
- 메모리 누수
- C언어
- AppSW
- Virtual Byte
- Cell Shader
- VR
- Rim Light
- OculusMotionVectorPass
- ColorGradingLutPass
- 가상 바이트
- URP
- working set
- ASW(Application SpaceWarp)
- 3d
- Three(Two) Tone Shading
- 개인 바이트
- Cartoon Rendering
- 게임 수학
- Cell Look
- URP로 변경
- Specular
- Windows Build
- Today
- Total
WinCNT
스레드 스케줄링 본문
동시성
어플리케이션의 설계 --> 어떤 작업을 어떻게 나눠서 스레드로 분배할 것인가
시스템(CPU, OS)의 설계 --> CPU 시간 자원을 각 스레드에 어떻게 할당할 것인가
CPU는 각각의 스레드를 시분할(Time Slice)과 작업의 상태로 관리한다
(작업의 상태를 확인해서 시분할로 작업을 맡기는 이미지)
다시 말해, 동시성의 기본은 상태와 시간!!
작업 데이터, 수행 코드 모두 메모리에 존재
CPU 안에도 메모리(레지스터)가 존재한다
윈도우 OS
윈도우 OS는 실시간 운영체제가 아니며, 선점형 운영체제이다
선점형 운영체제란 언제라도 특정 스레드를 정지하고 다른 스레드를 수행할 수 있다
즉, 선점형 운영체제는 어떤 스레드가 언제, 얼마만큼 스케줄링 될지를 결정하기 위한 알고리즘이 필요하다
(Windows에서는 스레드에게 할당된 시간을 퀀텀 타임이라고 함)
스레드는 커널 오브젝트에 컨텍스트 구조체를 유지하는데
이 컨텍스트 구조체에는 스레드가 마지막으로 수행됐을 때의 CPU 레지스터들의 정보를 가지고 있다
컨텍스트 전환(Context Switch)
윈도우는 일정 시간마다 모든 스레드 커널 오브젝트 중 스케줄 가능 상태의 스레드 커널 오브젝트를 검색하고
이 중 하나를 선택하여 스레드 컨텍스트 구조체 내에 저장된 레지스터 값을 CPU 레지스터에 로드한다
이러한 작업을 컨텍스트 전환(Context Switch)이라고 한다
다시 특정 시간이 지나면 윈도우는 CPU 레지스터 정보를 스레드의 컨텍스트로 저장하고 스레드 수행을 정지한다
그리고 시스템은 다시 스케줄 가능 상태의 스레드 커널 오브젝트 중 하나를 다시 선택하고
스레드 커널 오브젝트 내에 저장되어 있는 레지스터 값을 CPU 레지스터로 다시 로드한다
이러한 동작은 시스템이 부팅되고 종료될 때까지 계속되서 반복된다
스레드 스케줄링의 기준
주어진 시간 내에 스레드를 수행시키는 것이 가능한가?
즉, 특정 스레드를 거의 즉각적으로 실행할 수 있는가?
답변은 "그렇게 할 수 없다"이다
생성한 스레드가 항상 수행되고 있다고 가정해서는 안 된다는 것을 명심하자
시스템은 스케줄 가능한 스레드에 대해서만 스케줄링을 수행한다
수행할 필요가 없는 스레드에 대해서는
정지 카운트를 올려서 스케줄링이 될 수 없도록 만든다
정지된 스레드와 함께 대부분의 스레드는 이벤트가 발생하기를 기다리고 있는 상태이므로
스케줄링이 불가능한 상태(Ready 상태)가 된다
이러한 상태에는 퀀텀 타임이 주어지지 않는다
스레드의 정지와 계속 수행
정지 카운트(Suspend count)
스레드 커널 오브젝트 내에는 정지 카운트(Suspend count)라는 값이 저장되어 있다
스레드를 생성하면 스레드 커널 오브젝트가 생성되고 정지 카운트가 1로 초기화 된다
(즉, 스레드는 스케줄 불가능한 상태임)
시스템이 스레드를 완전히 초기화하려면 일정 시간이 필요하고
스레드가 완전히 준비될 때까지는 스레드를 수행하고 싶지 않을 것이기 때문에
스레드가 정지 상태로 유지하는 것이다
ResumeThread/SuspendThread 함수
SuspendThread는 정지 카운트를 +1를 하며, ResumeThread는 -1를 한다
주의해야 할 점은 SuspendThread를 호출한 만큼 ResumeThread를 해줘야
정지 카운트를 0으로 만들 수 있다는 것이다
ResumeThread/SuspendThread 함수를 이용하면 스레드 풀(Pool)를 구현할 수 있다
스레드를 미리 Pool로 만들어 놓으면 스레드 생성/삭제의 비용을 아낄 수 있다
이 때 만들어 둔 스레드를 SuspendThread로 정지시키고,
필요할 때 ResumeThread로 정지 카운트를 줄이는 방식으로 구현할 수 있다
한편 SuspendThread 함수를 호출할 때는 세심한 주의가 필요하다
이 함수는 자신뿐만 아니라 다른 스레드도 정지 가능한데
문제는 수행 중인 스레드가 어떤 작업을 수행하는 중에 정지되는지 알 수 없다는 점이다
예를 들어, 스레드가 힙으로부터 메모리를 할당 중에 SuspendThread로 정지되면 이 스레드는 힙을 Lock하게 된다
이 때, 다른 스레드가 힙에 접근하려고 하면 첫 번째 스레드가 다시 수행되기 전까지는 수행이 정지된다
이는 각종 문제와 데드락(Dead Lock)를 야기한다
프로그램의 흐름을 확실히 파악하고 컨트롤 할 수 있는 특수한 상황에서만
SuspendThread 함수를 사용하도록 하자
스레드 우선순위
모든 스레드는 0(가장 낮음)~31(가장 높음)의 우선순위 번호가 부여된다
시스템은 다음에 수행할 때 가장 높은 번호 순으로 스케줄 가능한 스레드를 선택한다
높은 우선순위의 스레드가 스케줄링이면 그보다 낮은 스레드는 절대로 CPU 시간을 할당 받지 못한다
이러한 상황를 기아 상태(Starvation)라고 한다
스레드를 CPU 코어의 개수를 고려해서 만드는 이유도 그러한 상황을 방지하기 위해서이다
하지만 그렇다고 낮은 우선순위의 스레드가 절대 수행되지 않는 것은 아니다
시스템의 대부분의 스레드은 스케줄 불가능한 상태(Ready 상태)를 유지한다
예를 들어 주 스레드가 GetMessage를 호출하고 있고,
시스템이 처리되지 않은 어떻나 메시지도 발견하지 못하면
참고로 시스템 부팅 시, 제로 페이지 스레드(Zero Page Thread)라고 불리는 특별한 스레드가 생성된다
우선순위의 추상적인 의미
MS는 세월의 흐름에 따라 컴퓨터의 사용 목적이 변경되고
스케줄링 알고리즘이 변경되더라도 소프트웨어가 정상 작동하도록 설계해야 했다
-
MS는 스케줄러의 동작 방식에 대해 완벽한 문서화를 제공하지 않음
-
어플리케이션이 스케줄러의 기능상 장점을 완벽하게 이용하지 못하도록 함
-
스케줄러의 알고리즘은 변경될 수 있으므로 방어적인 코딩을 할 것을 지속적으로 강조함
MS API는 시스템과 스케줄러에 대해 매우 추상적인 모습만을 드러내고 있다
어플리케이션을 설계할 때 스레드의 응답성과 「프로세스」의 우선순위를 고려해야 한다
다시 말해, 최종 사용자가 다른 어플리케이션과 동시에 사용할 가능성에 대해 고려해야 한다
이런 상황을 고려하여 개발할 어플리케이션 내의 스레드가 어느 정도의 응답성이 요구되는지 판단하고
이를 기준으로 프로세스의 우선순위 클래스(Priority Class)를 결정해야 한다
(MS는 추후 문제 소지가 있을 만한 부분에 대해서는 어떠한 보장도 하지 않음)
예를 들어 클라이언트 서버에 비해 모니터링 서버의 우선순위를 낮추는 것이 좋음
물론 기아 상태가 되지 않도록 고려도 해야 한다(비록 잘 발생하지 않는고 해도)
높은 우선순위 클래스는 반드시 필요한 경우에 한에서만 사용해야 한다
스레드 우선순위는 프로세스 우선순위에 상대적이다
프로세스에는 프로세스 우선순위 클래스가 할당되고,
스레드에는 우선순위에 상대적인 스레드 우선순위가 할당된다
CONTEXT 구조체는 왜 필요할까?
문맥 전환(Context Switching)에 필요한 정보들을 저장되어 있는 것이 CONTEXT 구조체이다
문맥 전환은 유저가 시스템 콜을 하지 않아도 시스템(커널)이 수행한다
프로세스에는 프로세스 우선순위 클래스로,
스레드에는 스레드 우선순위로 각 스레드의 최종 우선순위 레벨 값이 정해진며
윈도우OS는 이 우선순위 레벨 값으로 스레드를 스케줄링하며, 필요에 따라서는 문맥 전환을 발생시킨다
문맥 전환에는 프로세스 간의 문맥 전환과 스레스 간의 문맥 전환이 존재한다
프로세스 간의 문맥 전환에는 PCB가 세이브/로드되며,
스레스 간의 문맥 전환은 TCB가 세이브 로드 된다
비용은 로드할 데이터가 상대적으로 더 적은 스레스 간의 문맥 전환이 더 낮다고 할 수 있다
PCB란?
-
운영체제가 프로세스를 제어하기 위해 정보를 저장해 놓은 곳으로, 프로세스의 상태 정보를 저장하는 구조체
-
프로세스 상태 관리와 문맥 전환을 위해 필요
-
PCB는 프로세스 생성 시 만들어지며 주기억장치에 유지됨
참고 사이트) https://jhnyang.tistory.com/33
슬리핑
스레드는 Sleep 함수를 호출하여 일정 시간 동안 자신을 스케줄하지 않도록 운영체제에게 명령을 내릴 수 있다
-
Sleep을 호출하면 스레드는 자발적으로 남은 타임 슬라이스를 포기한다
-
시스템은 지정된 시간 동안 스레드를 스케줄 불가능 상태(Waiting 상태)로 유지한다
즉, 지정된 시간이 지나도 다시 스케줄 가능한 상태로 되는 것일 뿐, 스레드가 바로 재개하는 것은 아니다 -
Sleep 함수의 매개변수로 0을 전달할 수 있다
이렇게 하면 이 함수를 호출한 스레드가 남은 타임 슬라이스를 자발적으로 포기한다
즉, 시스템이 다른 스레드를 스케줄하게 하여 다른 스레드로 전환될 가능성이 생긴다
(우선순위가 낮은 스레드 밖에 없으면 다시 호출 될 수도 있다)
다른 스레드로의 전환
SwitchToThread 함수
윈도우는 스케줄 가능 상태에 있는 다른 스레드를 수행하기 위한 SwitchToThread 함수를 제공한다
이 함수를 호출하면 시스템은 일정 시간 동안 CPU 시간을 받지 못하여 수행되지 못한 스레드가 있는지 확인한다
만일 그렇나 스레드가 없다면 SwitchToThread 함수는 바로 반환되지만,
그러한 스레드가 존재한다면 SwitchToThread는 해당 스레드를 스케줄한다
CPU 시간을 할당받은 스레드는 단일 퀀텀 시간 동안만 수행되며,
이후 스케줄러는 이전과 동일하게 스케줄링을 수행한다
우선순위 프로그래밍
동적인 우선순위 레벨 변경
윈도우는 스레드의 우선순위 레벨을 가져오는 함수를 제공하지 않는다
시스템은 스레드의 우선순위 레벨을 스레드의 상대 우선순위와 스레드가 속한 프로세스의 우선순위 클래스 결합하여 산출한다
(이 값을 스레드의 기본 우선순위라고도 부른다)
스레드의 기본 우선순위 레벨이 13이라고 해도 그 스레드가 키보드의 입력을 받게 되면
스레드의 현재 우선순위 레벨은 2만큼 상승되어 스레드의 우선순위 레벨이 15가 된다
참고로 시스템이 스레드 우선순위 레벨의 동적 상승을 못하게 하는 두 개의 함수
SetProcessPriorityBoost 함수, bDisablePriorityBoost 함수를 제공하고 있다
또한 동적인 우선순위 레벨 상승 기능의 사용 여부를 확인하기 위한 두 개의 함수
GetProcessPriorityBoost 함수, GetThreadPriorityBoost 함수도 존재한다
포그라운드 프로세스를 위한 스케줄러 변경
어떤 프로세스가 윈도우를 가지고 있고 사용자가 그 윈도우를 이용하여 작업을 수행한다면
이러한 프로세스를 포그라운드 프로세스(Foreground Process)라고 한다
(반대되는 개념은 백그라운드 프로세스임)
윈도우는 포그라운드 프로세스에 대한 스레드 스케줄링 알고리즘을 변경하여
포그라운드 프로세스에게 좀 더 긴 퀀텀 시간을 제공할 수 있도록 하고 있다
(단, 포그라운드 프로세서가 보통 우선순위 클래스에서 수행될 때만 적용된다)
클라이언트에 필요한 스케줄링은 포그라운드(프로그램)이고,
서버에 필요한 스케줄링은 백그라운드(백그라운드 서비스)일 것이다
I/O 요청 우선순위 스케줄링
I/O 요청은 일반적으로 장시간 처리가 필요하며,
이 경우 낮은 우선순위의 스레드가 높은 우선순위의 스레드의 수행에 영향을 주기도 한다
이러한 이유로 장시간 동안 낮은 우선순위로 I/O 작업(디스크 조각 모음, 내용 인덱싱 등)이
수행되면 머신의 응답성이 나빠진다
윈도우 OS는 스레드가 I/O 요청에 대한 우선순위를 지정할 수 있게 한다
선호도(Affinity)
기본적으로 윈도우 OS는 스레드를 프로세서에 할당할 때 소프트 선호도(Soft Affinity)를 사용한다
이것은 다른 조건이 모두 동일하다면(사실 이러한 상황이 그다지 많지는 않지만 )
마지막으로 스레드를 수행했던 프로세서가 동일 스레드를 다시 수행하도록하는 것을 말한다
이로써 캐시 친화도를 높여서 캐시 히트율을 높일 수 있다
한편 어느 스레드를 어떤 CPU에서 수행할지를 제어하는 방법도 존재한다
이를 하드 선호도(Hard Affinity)라고 한다
다만, 대부분의 환경에 CPU 선호도를 변경하면, 스케줄링에 악영향을 미친다
그래도 CPU 테스트 등에도 사용할 수 있다
번외) 시간 제어에 대해서
시간 제어에 대해서는 2가지 측면이 존재한다
주기적(Interval)과 시간 측정(Ontime)이다
번외) APC, 입출력 완료 통지
APC --> 비동기, GetMessage
입출력 완료 통지 --> 파일이나 네트워크 --> 요청 완료를 대기 --> OS가 알려줌
'게임 프로그래밍(학습 내용 정리) > 시스템 프로그래밍' 카테고리의 다른 글
유저 모드에서의 스레드 동기화 (0) | 2022.03.29 |
---|---|
윈도우OS의 시간 측정 함수 조사 (0) | 2022.03.28 |
Exception Filter (0) | 2022.03.22 |
실제 핸들과 허위 핸들(Pseudo Handle) (0) | 2022.03.22 |
프로세스와 스레드 (0) | 2022.03.21 |