WinCNT

프로세스와 스레드 본문

게임 프로그래밍(학습 내용 정리)/시스템 프로그래밍

프로세스와 스레드

WinCNT_SSS 2022. 3. 21. 18:02

실제 작업하는 것은 스레드

그래서 worker thread라는 용어가 존재한다

 

스레드는 왜 필요한가?

 

모든 프로세스는 적어도 하나 이상의 스레드를 사용한다

스레드는 프로세스의 주소 공간 내에 있는 코드를 수행한다

 

스레드는 프로세스와 유사하게 두 개의 요소로 구성되어 있다

  • 운영체제가 스레드를 다루기 위해 사용하는 스레드 커널 오브젝트
  • 스레드가 코드를 수행할 때 함수의 매개변수와 지역변수를 저장하기 위한 스레드 스택

실행 코드와 데이터는 한 세트

 

함수 스택, 콜 스택 - 스레드 스택

함수 내부가 실행되는 건 스레드가 기반

 

비동기를 구현하기 위해 만들어진 것은 스레드 이외에도 존재한다

Co-routine 등

 

본격적으로 스레드가 사용되기 시작한 것은 하드웨어(CPU)의 개수가 2개 이상이 될 때부터

하지만 CPU가 1개였을 때에도 스레드는 존재했다

동기와 비동기에 대한 문제 등등

 

스레드를 사용하기 위해서는 설계부터 잘 고민해야 한다

  • 무엇을 동시에 할 것인가
  • 어떻게 동시에 할 것인가

예를 들어 단팥빵을 만든다고 할 때,

반죽하는 작업 / 팥 앙금을 만드는 작업으로 나눌 수 있을 것이다

그렇다면 반죽 - 팥 앙금 작업을 동시에 여러 명이 하는 방법이 있을테고

반죽과 팥 앙금 작업을 나눠서 하는 방법이 있을 것이다

이렇듯 동시에 한다고 해서 여러 방법이 존재한다

 

 

 

프로세스와 스레드


프로세스

프로세스는 일반적으로 수행 중인 프로그램의 인스턴스라고 정의

프로세스를 관리하기 위한 목적으로 운영 체제가 사용하는 프로세스 커널 오브젝트
실행 모듈이나 DLL의 코드와 데이터를 수용하는 주소 공간(스레드 스택, 힙 할당)

힙 영영은 공용 공간 - 포인터만 있으면 어떤 스레드에서도 접근 가능하다

 

스레드

스레드는 프로세스의 주소 공간 내의 코드를 수행
운영체제가 스레드를 다루기 위한 스레드 커널 오브젝트
스레드가 코드를 수행할 때 함수의 매개변수와 지역변수를 저장하기 위한 스레드 스택

 

 

스레드의 필요성


프로세스는 자신만의 주소 공간을 가지기 때문에 많은 시스템 리소스를 사용한다

반면, 스레드는 프로세스에 비해 상당히 적은 시스템 리소스가 필요하다

(커널 오브젝트와 스레드 스택 정도만 필요함)

 

하지만 스레드가 프로세스에 비해 부하가 적다고 해도

경우에 따라서는 여러 개의 프로세스를 이용하는 방법이 더 좋기도 하다

따라서 추가적으로 스레드를 생성하는 설계를하기 전에

다음과 같은 판단 기준으로 잘 생각해 보는 것이 좋다

 

판단의 기준 1. 성능 이슈

스레드를 사용하면 필연적으로 복잡도 증가하며 구현의 어려워진다

즉, 안전성 확보가 중요하다

 

판단의 기준 2. 구조 설계와 전략

설계가 구체적이어야 하며, 설계에 대해 논리적인 설명이 가능해야 한다

또한 예외 상황에 대한 고려가 필요하다

 

스레드가 사용된 예

스레드가 사용된 구체적인 예로는 백그라운드 서비스, Visual Studio의 컴파일 에러 체크,

엑셀의 재계산 작업, 디스크 조각 모음의 백그라운드 수행 등이 있다

(대체적으로 백그라운드 작업 처리를 할 때 사용된 경우가 많음)

 

반대로 스레드를 이용한 UI(유저 인터페이스) 구현은 가능한 피하는 것이 좋다

 

게임에서 스레드가 사용되는 예

렌더 스레드 - 물리 스레드

렌더 스레드 - 물리 스레드 등에 사용될 수 있다

(물론 모든 렌더 스레드, 물리 스레드에 사용하지 않음)

물리 연산이 비쌀 때, 렌더 스레드에 영향을 방지하기 위해 사용할 수 있다

(새 떼를 구현할 때 등)

 

리소스 로딩, 맵 이동

스레드를 이용하여 백그라운드로 일부 리소스를 미리 메모리에 로딩해두는 방법이 있다

전부 로딩하면 스타팅이 오래 걸리고, 전부 필요할 때만 로딩하면 이동에 오래 걸리는데

그걸 방지할 수 있음

 

게임 서버 구조

분산 구조 - 프로세스 분산

내부 구조 - 멀티 스레드 분산

 

프로세스 분산 - 기능 분산, 확장성을 위해 주로 사용

프로세스 분산은 물리적으로 분산이 가능하다는 말이기도 하다

(스레드로 분산하면 물리적으로 분산이 불가능함)

 

예) 로그인 서버 - 게임 서버의 분산

복잡도(예외 처리 등)는 올라가지만 확장성의 증가가 더 크기 때문에 구현한다(...)

 

스레드 분산

기능 분산과 병렬 분산

방(대기실)을 만드는 스레드를 만들었을 경우,

CPU가 많을 때 여러 스레드를 사용할 수 있게 된다

 

 

 

스레드 함수


작업 스레드가 수행하는 것은 함수 단위이므로 함수가 필요하다

또한 함수는 리턴 타입, 함수 이름, 파라메터가 필요하며 함수의 호출은 커널이 수행할 것이다

 

스레드가 수행하는 것은 함수 단위

스레드는 수행할 함수를 커널에게 맡긴다

그러면 커널이 수행할 수 있을 때 수행할 것이다

 

작업 흐름을 제어하는 방법에는 2가지가 있다

스레드 간의 통신을 통해 스레드의 작업 시작을 컨트롤하는 방법

API를 이용하여 커널에게 맡길 때 플래그로 스레드의 작업 시작을 컨트롤하는 방법

 

 

 

스레드의 생성


스레드를 생성하는 함수는 3가지가 있다

CreateThread 함수, _beginthread 함수, _beginthreadex 함수

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread

 

CreateThread function (processthreadsapi.h) - Win32 apps

Creates a thread to execute within the virtual address space of the calling process.

docs.microsoft.com

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/beginthread-beginthreadex?view=msvc-170 

 

_beginthread, _beginthreadex

Learn more about: _beginthread, _beginthreadex

docs.microsoft.com

 

하지만 결론부터 말하자면 _beginthreadex 함수을 사용하는 것이 좋다

 

일단 CreateThread 함수는 다음과 같은 매개변수를 갖는다

HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  [in]            SIZE_T                  dwStackSize,
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,
  [in]            DWORD                   dwCreationFlags,
  [out, optional] LPDWORD                 lpThreadId
);

여기서 중요한 것은 lpStartAddress와 lpParameter이다

 

lpStartAddress는 스레드가 수행할 함수 포인터를 지정한다

lpParameter에는 사라질 가능성이 없는 변수(주소)를 넘겨야 한다

(그래서 보통 Heap 영역의 주소를 넘기며, Stack 영역의 주소를 넘기진 않음)

 

코드 생성 - 런타임 라이브러리

서버 - 다중 스레드(/MT) = 설치가 힘들 때

클라이언트 - 다중 스레드 DLL(/MD) = 설치가 가능할 때

 

 

 

스레드의 내부


스레드의 함수 포인터는 커널 입장에서는 main 함수와 같은 진입점과 같다

 

스레드는 내부에서 스레드의 흐름을 컨트롤할 수 있지만

외부에서도 핸들을 가지고 컨트롤할 수 있다

 

 

 

C/C++ 런타임 라이브러리에 대한 고찰


참고 사이트)

https://docs.microsoft.com/ko-kr/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170 

 

/MD, -MT, -LD(Run-Time 라이브러리 사용)

자세한 정보: /MD, /MT, /LD(Run-Time 라이브러리 사용)

docs.microsoft.com

 

https://docs.microsoft.com/ko-kr/cpp/windows/deployment-concepts?view=msvc-170 

 

배포 개념

자세한 정보: 배포 개념

docs.microsoft.com

 

 

 

번외) Resource acquisition is initialization(RAII)


RAII는 디자인 패턴 중 하나

리소스의 획득과 해방은 클래스형 변수의 초기화와 파괴 처리에 연결한다라는 디자인 패턴이다

 

참고 사이트)

https://docs.microsoft.com/ko-kr/cpp/cpp/welcome-back-to-cpp-modern-cpp?view=msvc-170 

 

C++ 시작하기 - 최신 C++

최신 C++의 새로운 프로그래밍 관용구와 그 근거를 설명합니다.

docs.microsoft.com

https://dydtjr1128.github.io/cpp/2020/04/05/Cpp-lock.html

 

C++ 락(std::lock, std::unique_lock, std::lock_guard, condition_variable...) - dydtjr1128's Blog

Intro std에 존재하는 lock 방법들은 운영체제 단에서 지원하는 크리티컬 섹션을 래핑한 mutex를 기반으로 개발자가 쉽게 락을 걸 수 있도록 도와줍니다. 앞서 배운...

dydtjr1128.github.io

https://lipcoder.tistory.com/entry/DirectX-12-COMComponent-Object-Model

 

[DirectX 12] 기본지식 - COM(Component Object Model)

COM(Component Object Model)은 DirectX의 프로그래밍 언어 독립성과 하위 호환성을 가능하게 하는 기술이다. C++ 클래스로 간주하고 사용해도 무방하기 때문에 주로 COM 객체라고 흔히 부른다.  COM 객체는

lipcoder.tistory.com

 

 

 

 

 

 

 

 

 

SSS