수년 동안 프로세서의 성능은 꾸준히 증가했으며, 게임 및 기타 프로그램은 특별한 작업을 수행하지 않고도 이 증가하는 전력의 이점을 거두었습니다.
규칙이 변경되었습니다. 단일 프로세서 코어의 성능은 이제 매우 느리게 증가하고 있습니다. 그러나 일반적인 컴퓨터 또는 콘솔에서 사용할 수 있는 컴퓨팅 성능은 계속 증가하고 있습니다. 차이점은 이러한 성능 향상의 대부분은 단일 머신에 여러 프로세서 코어를 갖는 것에서 비롯되며, 종종 단일 칩에 있다는 것입니다. Xbox 360 CPU는 하나의 칩에 3개의 프로세서 코어를 가지고 있으며, 2006년에 판매된 PC 프로세서의 약 70%가 멀티 코어였습니다.
사용 가능한 처리 능력의 증가는 과거와 마찬가지로 극적이지만, 이제 개발자는 이 기능을 사용하기 위해 다중 스레드 코드를 작성해야 합니다. 다중 스레드 프로그래밍은 새로운 디자인 및 프로그래밍 과제를 제공합니다. 이 항목에서는 다중 스레드 프로그래밍을 시작하는 방법에 대한 몇 가지 조언을 제공합니다.
좋은 디자인의 중요성
좋은 다중 스레드 프로그램 디자인은 중요하지만 매우 어려울 수 있습니다. 주요 게임 시스템을 다른 스레드로 우연히 이동하는 경우 각 스레드가 다른 스레드를 기다리는 데 대부분의 시간을 소비하는 것을 발견할 수 있습니다. 이러한 유형의 설계로 인해 복잡성이 증가하고 디버깅 작업이 상당히 늘어나고 성능이 거의 향상되지 않습니다.
스레드가 데이터를 동기화하거나 공유해야 할 때마다 데이터 손상, 동기화 오버헤드, 교착 상태 및 복잡성이 발생할 수 있습니다. 따라서 다중 스레드 디자인은 모든 동기화 및 통신 지점을 명확하게 문서화해야 하며 가능한 한 이러한 점을 최소화해야 합니다. 스레드가 통신해야 하는 경우 코딩 작업이 증가하여 너무 많은 소스 코드에 영향을 줄 경우 생산성이 저하될 수 있습니다.
다중 스레딩의 가장 간단한 설계 목표는 코드를 독립적으로 큰 단위로 분할하는 것입니다. 그런 다음 이러한 부분을 프레임당 몇 번만 통신하도록 제한하면 과도한 복잡성 없이 다중 스레딩에서 상당한 속도 향상을 볼 수 있습니다.
일반적인 스레드 작업
몇 가지 유형의 작업은 별도의 스레드에 배치할 수 있는 것으로 입증되었습니다. 다음 목록은 완전하지는 않지만 몇 가지 아이디어를 제공해야 합니다.
렌더링
장면 그래프를 탐색하거나 D3D 함수 호출만 포함할 수 있는 렌더링은 CPU 시간의 50% 이상을 차지하는 경우가 많습니다. 따라서 렌더링을 다른 스레드로 이동하면 상당한 이점이 있을 수 있습니다. 업데이트 스레드는 렌더링 스레드가 처리할 수 있는 일종의 렌더링 설명 버퍼를 채울 수 있습니다.
게임 업데이트 스레드는 항상 렌더링 스레드보다 한 프레임 앞서 있으므로 사용자 작업이 화면에 표시되기 전에 두 프레임이 걸립니다. 이 증가된 대기 시간은 문제가 될 수 있지만 워크로드를 분할할 때 프레임 속도가 증가하면 일반적으로 총 대기 시간이 허용됩니다.
대부분의 경우 모든 렌더링은 여전히 단일 스레드에서 수행되지만 게임 업데이트와는 다른 스레드입니다.
D3DCREATE_MULTITHREADED 플래그는 한 스레드에서 렌더링을 허용하고 다른 스레드에서 리소스를 만드는 데 사용되는 경우도 있습니다. 이 플래그는 Xbox 360에서 무시되며 Windows에서 사용하지 않아야 합니다. Windows에서 이 플래그를 지정하면 D3D가 동기화에 상당한 시간을 소비하므로 렌더링 스레드가 느려집니다.
파일 압축 해제
로드 시간은 항상 너무 길며 프레임 속도에 영향을 주지 않고 데이터를 메모리로 스트리밍하는 것은 어려울 수 있습니다. 모든 데이터가 디스크에서 적극적으로 압축되는 경우 하드 드라이브 또는 광학 디스크의 데이터 전송 속도는 제한 요인이 될 가능성이 적습니다. 단일 스레드 프로세서에서는 일반적으로 로드 시간을 돕기 위해 압축에 사용할 수 있는 프로세서 시간이 충분하지 않습니다. 그러나 다중 프로세서 시스템에서 파일 압축 해제는 낭비되는 CPU 주기를 사용합니다. 로드 시간 및 스트리밍을 향상시킵니다. 디스크의 공간을 절약합니다.
프로덕션 중에 수행해야 하는 처리를 대체하기 위해 파일 압축 해제를 사용하지 마세요. 예를 들어 레벨 로드 중에 XML 데이터를 구문 분석하는 데 추가 스레드를 사용하는 경우 플레이어의 환경을 개선하기 위해 다중 스레딩을 사용하지 않습니다.
파일 압축 해제 스레드를 사용하는 경우 데이터 읽기 효율성을 최대화하기 위해 비동기 파일 I/O 및 대용량 읽기를 계속 사용해야 합니다.
그래픽 보풀
게임의 시각적 효과를 개선하지만 꼭 필요하지는 않은 많은 화려한 그래픽 요소들이 있습니다. 여기에는 절차적으로 생성된 클라우드 애니메이션, 천 및 모발 시뮬레이션, 절차적 파도, 절차적 식물, 더 많은 입자 또는 비게임 플레이 물리학과 같은 것들이 포함됩니다.
이러한 효과는 게임 플레이에 영향을 주지 않으므로 까다로운 동기화 문제를 일으키지 않으며 프레임당 한 번 또는 덜 자주 다른 스레드와 동기화할 수 있습니다. 또한 Windows용 게임에서 이러한 효과는 멀티코어 CPU를 사용하는 게이머에게 가치를 더할 수 있으며, 단일 코어 컴퓨터에서는 자동으로 생략되므로 다양한 기능에서 쉽게 확장할 수 있습니다.
물리학
일반적으로 게임 업데이트에는 물리학 계산 결과가 즉시 필요하기 때문에 게임 업데이트와 병렬로 실행하기 위해 물리학을 별도의 스레드에 배치할 수 없는 경우가 많습니다. 다중 스레딩 물리학의 대안은 여러 프로세서에서 실행하는 것입니다. 이 작업을 수행할 수 있지만 공유 데이터 구조에 자주 액세스해야 하는 복잡한 작업입니다. 물리학 워크로드를 주 스레드에 맞게 충분히 낮게 유지할 수 있다면 작업이 더 간단해질 것입니다.
여러 스레드에서 물리학 실행을 지원하는 라이브러리를 사용할 수 있습니다. 그러나 이로 인해 문제가 발생할 수 있습니다. 게임이 물리학을 실행할 때 많은 스레드를 사용하지만 나머지 시간에는 거의 사용되지 않습니다. 여러 스레드에서 물리학을 실행하려면 워크로드가 프레임에 균등하게 분산되도록 이 문제를 해결해야 합니다. 다중 스레드 물리학 엔진을 작성하는 경우 모든 데이터 구조, 동기화 지점 및 부하 분산에 주의해야 합니다.
다중 스레드 디자인 예제
Windows용 게임은 CPU 코어 수가 다른 컴퓨터에서 실행해야 합니다. 2코어 컴퓨터의 수가 빠르게 증가하고 있지만 대부분의 게임 머신에는 여전히 하나의 코어만 있습니다. Windows용 일반적인 게임은 추가 기능을 추가하기 위한 선택적 작업자 스레드를 사용하여 업데이트 및 렌더링을 위해 워크로드를 하나의 스레드로 분할할 수 있습니다. 또한 파일 I/O 및 네트워킹을 수행하기 위한 일부 백그라운드 스레드가 사용될 수 있습니다. 그림 1은 기본 데이터 전송 지점과 함께 스레드를 보여 줍니다.
그림 1. Windows용 게임에서 스레드 디자인
일반적인 Xbox 360 게임은 추가 CPU 집약적 소프트웨어 스레드를 사용할 수 있으므로 그림 2와 같이 워크로드를 업데이트 스레드, 렌더링 스레드 및 3개의 작업자 스레드로 분할할 수 있습니다.
그림 2. Xbox 360 게임의 스레드 디자인
xbox 360 게임에서의 스레드 디자인
파일 I/O 및 네트워킹을 제외하고 이러한 작업은 모두 CPU 집약적일 가능성이 있으므로 자체 하드웨어 스레드에서 이점을 얻을 수 있습니다. 또한 이러한 작업은 통신하지 않고 전체 프레임에 대해 실행할 수 있을 만큼 독립적일 수 있습니다.
게임 업데이트 스레드는 컨트롤러 입력, AI 및 물리학을 관리하고 다른 4개의 스레드에 대한 지침을 준비합니다. 이러한 지침은 게임 업데이트 스레드가 소유한 버퍼에 배치되므로 지침이 생성될 때 동기화가 필요하지 않습니다.
프레임의 끝에서 게임 업데이트 스레드는 명령 버퍼를 다른 4개의 스레드로 전달한 다음, 다음 프레임에서 작업을 시작하여 다른 명령 버퍼 집합을 채웁니다.
업데이트 및 렌더링 스레드는 서로 동기화되어 작동하므로 통신 버퍼는 단순히 이중 버퍼링됩니다. 어느 순간, 업데이트 스레드가 하나의 버퍼를 채우는 동안 렌더링 스레드는 다른 버퍼에서 읽고 있습니다.
다른 작업자 스레드는 반드시 프레임 속도에 연결되지는 않습니다. 데이터 조각을 압축 해제하는 데 프레임보다 훨씬 적게 걸리거나 많은 프레임이 필요할 수 있습니다. 덜 빈번한 업데이트가 매우 용인될 수 있으므로 의류와 머리 시뮬레이션도 프레임 속도로 정확하게 실행할 필요가 없습니다. 따라서 이러한 세 스레드는 업데이트 스레드 및 렌더링 스레드와 통신하기 위해 서로 다른 데이터 구조가 필요합니다. 각각 작업 요청을 저장할 수 있는 입력 큐가 필요하며, 렌더링 스레드에는 스레드에서 생성된 결과를 저장할 수 있는 데이터 큐가 필요합니다. 각 프레임의 끝에서 업데이트 스레드는 작업자 스레드의 큐에 작업 요청 블록을 추가합니다. 프레임당 한 번만 목록에 추가하면 업데이트 스레드가 동기화 오버헤드를 최소화할 수 있습니다. 각 작업자 스레드는 다음과 같은 루프를 사용하여 가능한 한 빨리 작업 큐에서 할당을 가져옵니다.
for(;;)
{
while( WorkQueueNotEmpty() )
{
RemoveWorkItemFromWorkQueue();
ProcessWorkItem();
PutResultInDataQueue();
}
WaitForSingleObject( hWorkSemaphore );
}
데이터가 업데이트 스레드에서 작업자 스레드로 이동하고 렌더링 스레드로 전환되므로 일부 작업이 화면으로 전환되기 전에 3개 이상의 프레임이 지연될 수 있습니다. 그러나 작업자 스레드에 대기 시간 허용 작업을 할당하는 경우에는 문제가 되지 않습니다.
다른 디자인은 여러 워커 스레드가 모두 동일한 작업 큐에서 작업하도록 하는 것입니다. 이렇게 하면 자동 부하 분산이 제공되고 모든 작업자 스레드가 사용 중일 가능성이 높아질 수 있습니다.
게임 업데이트 스레드는 작업자 스레드에 너무 많은 작업을 제공하지 않도록 주의해야 합니다. 그렇지 않으면 작업 큐가 지속적으로 증가할 수 있습니다. 업데이트 스레드가 이를 관리하는 방법은 작업자 스레드가 수행하는 작업의 종류에 따라 달라집니다.
동시 다중 스레딩 및 스레드 수
모든 스레드는 동일하게 만들어지지 않습니다. 두 개의 하드웨어 스레드는 별도의 칩, 동일한 칩 또는 동일한 코어에 있을 수 있습니다. 게임 프로그래머가 알아야 할 가장 중요한 구성은 하나의 코어에 두 개의 하드웨어 스레드가 있는 것으로, 이는 동시 다중 스레딩(SMT) 또는 HT 기술(Hyper-Threading Technology)입니다.
SMT 또는 HT 기술 스레드는 CPU 코어의 리소스를 공유합니다. 실행 단위를 공유하기 때문에 두 개의 독립 하드웨어 스레드에서 가능한 100% 대신 두 스레드를 실행하는 최대 속도는 일반적으로 10~20%입니다.
더 중요한 것은 SMT 또는 HT 기술 스레드가 L1 명령 및 데이터 캐시를 공유합니다. 메모리 액세스 패턴이 호환되지 않는 경우 캐시를 놓고 다투게 되고 많은 캐시 누락이 발생할 수 있습니다. 최악의 경우 두 번째 스레드가 실행될 때 CPU 코어의 총 성능이 실제로 저하될 수 있습니다. Xbox 360에서 이것은 매우 간단한 문제입니다. Xbox 360의 구성은 각각 두 개의 하드웨어 스레드가 있는 세 개의 CPU 코어로 알려져 있으며 개발자는 소프트웨어 스레드를 특정 CPU 스레드에 할당하고 스레딩 디자인이 추가 성능을 제공하는지 여부를 측정할 수 있습니다.
Windows에서는 상황이 더 복잡합니다. 스레드 수와 해당 구성은 컴퓨터마다 다르며 구성을 결정하는 것은 복잡합니다. GetLogicalProcessorInformation함수는 서로 다른 하드웨어 스레드 간의 관계에 대한 정보를 제공하며, 이 함수는 Windows Vista, Windows 7 및 Windows XP SP3에서 사용할 수 있습니다. 따라서 지금은 사용 가능한 "실제" 스레드 수를 결정하기 위해 Intel 및 AMD에서 제공하는 CPUID 명령 및 알고리즘을 사용해야 합니다. 자세한 내용은 참조를 참조하세요.
DirectX SDK의 CoreDetection 샘플에는 GetLogicalProcessorInformation 함수 또는 CPUID 명령을 사용하여 CPU 코어 토폴로지 반환을 사용하는 샘플 코드가 포함되어 있습니다. CPUID 명령은 getLogicalProcessorInformation 현재 플랫폼에서 지원되지 않는 경우에 사용됩니다. CoreDetection은 다음 위치에서 찾을 수 있습니다.
-
원본:
-
DirectX SDK 루트\Samples\C++\Misc\CoreDetection
-
실행 파일:
-
DirectX SDK 루트\Samples\C++\Misc\Bin\CoreDetection.exe
가장 안전한 가정은 CPU 코어당 CPU 집약적 스레드를 하나만 갖는 것입니다. CPU 코어보다 CPU 집약적인 스레드가 많으면 거의 또는 전혀 이점이 없으며 추가 스레드의 오버헤드와 복잡성이 더합니다.
스레드 만들기
스레드 만들기는 매우 간단한 작업이지만 많은 잠재적 오류가 있습니다. 아래 코드는 스레드를 만들고, 스레드가 종료되기를 기다린 다음, 정리하는 적절한 방법을 보여줍니다.
const int stackSize = 65536;
HANDLE hThread = (HANDLE)_beginthreadex( 0, stackSize,
ThreadFunction, 0, 0, 0 );
// Do work on main thread here.
// Wait for child thread to complete
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );
...
unsigned __stdcall ThreadFunction( void* data )
{
#if _XBOX_VER >= 200
// On Xbox 360 you must explicitly assign
// software threads to hardware threads.
XSetThreadProcessor( GetCurrentThread(), 2 );
#endif
// Do child thread work here.
return 0;
}
스레드를 만들 때 자식 스레드의 스택 크기를 지정하거나 0을 지정하는 옵션이 있습니다. 이 경우 자식 스레드는 부모 스레드의 스택 크기를 상속합니다. 스레드가 시작될 때 스택이 완전히 커밋되는 Xbox 360에서 0을 지정하면 많은 자식 스레드가 부모만큼 스택이 필요하지 않기 때문에 상당한 메모리가 낭비될 수 있습니다. Xbox 360에서는 스택 크기가 64KB의 배수여야 합니다.
CreateThread 함수를 사용하여 스레드를 만드는 경우 C/C++ 런타임(CRT)이 Windows에서 제대로 초기화되지 않습니다. 대신 CRT _beginthreadex 함수를 사용하는 것이 좋습니다.
CreateThread 또는 _beginthreadex 반환 값은 스레드 핸들입니다. 이 스레드는 자식 스레드가 종료될 때까지 기다리는 데 사용할 수 있습니다. 이는 스레드 상태를 확인하는 루프에서 회전하는 것보다 훨씬 간단하고 효율적입니다. 스레드가 종료되기를 기다리려면 스레드 핸들을 사용하여 WaitForSingleObject 호출하면 됩니다.
스레드의 리소스는 스레드가 종료되고 스레드 핸들이 닫혀 있을 때까지 해제되지 않습니다. 따라서 스레드 핸들의 사용이 끝나면 CloseHandle를 사용하여 닫아야 합니다. WaitForSingleObject를 사용하여 스레드가 종료될 때까지 기다리는 경우, 대기가 완료된 후에 핸들을 닫아야 합니다.
Xbox 360에서는 XSetThreadProcessor사용하여 소프트웨어 스레드를 특정 하드웨어 스레드에 명시적으로 할당해야 합니다. 그렇지 않으면 모든 자식 스레드가 부모와 동일한 하드웨어 스레드에 유지됩니다. Windows에서는 SetThreadAffinityMask 사용하여 스레드가 실행되어야 하는 하드웨어 스레드를 운영 체제에 강력하게 제안할 수 있습니다. 시스템에서 실행 중인 다른 프로세스가 무엇인지 모르기 때문에 이 기술은 일반적으로 Windows에서 피해야 합니다. 일반적으로 Windows 스케줄러가 스레드를 유휴 하드웨어 스레드에 할당하도록 하는 것이 좋습니다.
스레드 만들기는 비용이 많이 드는 작업입니다. 스레드를 만들고 제거해야 하는 경우는 거의 없습니다. 스레드를 자주 만들고 삭제하려는 경우 대신 작업을 기다리는 스레드 풀을 사용합니다.
스레드 동기화
여러 스레드가 함께 작동하려면 스레드를 동기화하고, 메시지를 전달하고, 리소스에 대한 단독 액세스를 요청할 수 있어야 합니다. Windows 및 Xbox 360에는 다양한 동기화 기본 형식 세트가 함께 제공됩니다. 이러한 동기화 기본 형식에 대한 자세한 내용은 플랫폼 설명서를 참조하세요.
단독 액세스
리소스, 데이터 구조 또는 코드 경로에 대한 단독 액세스 권한을 얻는 것이 일반적입니다. 배타적 액세스 권한을 얻기 위한 한 가지 옵션은 일반적인 사용법이 여기에 표시된 뮤텍스입니다.
// Initialize
HANDLE mutex = CreateMutex( 0, FALSE, 0 );
// Use
void ManipulateSharedData()
{
WaitForSingleObject( mutex, INFINITE );
// Manipulate stuff...
ReleaseMutex( mutex );
}
// Destroy
CloseHandle( mutex );
The kernel guarantees that, for a particular mutex, only one thread at a time can
acquire it.
The main disadvantage to mutexes is that they are relatively expensive to acquire
and release. A faster alternative is a critical section.
// Initialize
CRITICAL_SECTION cs;
InitializeCriticalSection( &cs );
// Use
void ManipulateSharedData()
{
EnterCriticalSection( &cs );
// Manipulate stuff...
LeaveCriticalSection( &cs );
}
// Destroy
DeleteCriticalSection( &cs );
중요한 섹션에는 뮤텍스와 비슷한 의미 체계가 있지만 프로세스 간이 아니라 프로세스 내에서만 동기화하는 데 사용할 수 있습니다. 그들의 주요 이점은 뮤텍스보다 약 20배 더 빠르게 실행한다는 것입니다.
이벤트
두 스레드(업데이트 스레드 및 렌더링 스레드)가 렌더링 설명 버퍼 쌍을 사용하여 번갈아 가며 수행되는 경우 특정 버퍼로 완료되는 시기를 나타내는 방법이 필요합니다. 이 작업은 이벤트(CreateEvent할당됨)를 각 버퍼와 연결하여 수행할 수 있습니다. 스레드가 버퍼로 완료되면 SetEvent 사용하여 신호를 보낼 수 있으며 다른 버퍼의 이벤트에서 WaitForSingleObject 호출할 수 있습니다. 이 기술은 리소스의 세 배 버퍼링을 쉽게 추정합니다.
세마포
세마포는 실행할 수 있는 스레드 수를 제어하는 데 사용되며 일반적으로 작업 큐를 구현하는 데 사용됩니다. 한 스레드는 큐에 작업을 추가하고 큐에 새 항목을 추가할 때마다 ReleaseSemaphore 사용합니다. 이렇게 하면 대기 중인 스레드 풀에서 하나의 작업자 스레드를 해제할 수 있습니다. 작업자 스레드는 WaitForSingleObject를 호출하고, 반환되면 큐에 작업 항목이 있다는 것을 알게 됩니다. 또한 공유 작업 큐에 안전하게 액세스할 수 있도록 중요한 섹션 또는 기타 동기화 기술을 사용해야 합니다.
SuspendThread 사용 자제
스레드가 수행하는 작업을 중지하려는 경우 올바른 동기화 기본 형식 대신 SuspendThread 사용하는 것이 좋습니다. 이것은 항상 나쁜 생각이며 교착 상태 및 기타 문제로 쉽게 이어질 수 있습니다. SuspendThread는 Visual Studio 디버거와도 문제가 있습니다. SuspendThread사용을 피하십시오. 대신 WaitForSingleObject 사용합니다.
WaitForSingleObject 및 WaitForMultipleObjects
WaitForSingleObject함수는 가장 일반적으로 사용되는 동기화 함수입니다. 그러나 여러 조건이 동시에 충족될 때까지 또는 조건 집합 중 하나가 충족될 때까지 스레드가 대기하도록 하는 경우가 있습니다. 이 경우 WaitForMultipleObjects를 사용해야 합니다.
연동 함수 및 잠금 없는 프로그래밍
잠금을 사용하지 않고 간단한 스레드 안전한 작업을 수행하기 위한 함수 모음이 있습니다. 이러한 함수는 Interlocked 제품군에 속하며, interlockedIncrement같은 함수가 포함됩니다. 이러한 함수와 플래그의 신중한 설정을 사용하는 다른 기술을 함께 잠금 없는 프로그래밍이라고 합니다. 잠금 없는 프로그래밍은 올바르게 수행하는 것이 매우 까다로울 수 있으며 Windows보다 Xbox 360에서 훨씬 더 어렵습니다.
잠금 없는 프로그래밍에 대한 자세한 내용은 Xbox 360 및 Microsoft Windows 대한잠금 없는 프로그래밍 고려 사항을 참조하세요.
동기화 최소화
일부 동기화 방법은 다른 방법보다 빠릅니다. 그러나 가능한 가장 빠른 동기화 기술을 선택하여 코드를 최적화하는 대신 일반적으로 동기화 빈도를 줄이는 것이 좋습니다. 이는 너무 자주 동기화하는 것보다 빠르며 디버그하기 쉬운 더 간단한 코드를 만듭니다.
메모리 할당과 같은 일부 작업은 올바르게 작동하기 위해 동기화 기본 형식을 사용해야 할 수 있습니다. 따라서 기본 공유 힙에서 자주 할당하면 동기화가 자주 수행되므로 성능이 저하됩니다. 빈번한 할당을 방지하거나 스레드당 힙 사용(HeapCreate를 사용하는 경우 HEAP_NO_SERIALIZE 사용)을 사용하면 숨겨진 동기화를 방지할 수 있습니다.
숨겨진 동기화의 또 다른 원인은 D3DCREATE_MULTITHREADED 때문에 Windows의 D3D에서 많은 작업에서 동기화를 사용합니다. (Xbox 360에서는 플래그가 무시됩니다.)
스레드 로컬 스토리지라고도 하는 스레드별 데이터는 동기화를 방지하는 중요한 방법이 될 수 있습니다. Visual C++를 사용하면 __declspec(스레드) 구문을 사용하여 전역 변수를 스레드당으로 선언할 수 있습니다.
__declspec( thread ) int tls_i = 1;
이렇게 하면 프로세스의 각 스레드가 자체 tls_i 복사본을 제공하므로 동기화를 요구하지 않고 안전하고 효율적으로 참조할 수 있습니다.
__declspec(스레드) 기술은 동적으로 로드된 DLL에서 작동하지 않습니다. 동적으로 로드된 DLL을 사용하는 경우 TLSAlloc 함수 제품군을 사용하여 스레드 로컬 스토리지를 구현해야 합니다.
스레드 삭제
스레드를 안전하게 종료하는 유일한 방법은 스레드 자체가 종료하는 것입니다; 이는 주 스레드 함수에서 반환하거나, 스레드가 ExitThread 또는 _endthreadex함수를 호출함으로써 이루어질 수 있습니다. _beginthreadex를 사용하여 스레드를 만드는 경우, _endthreadex를 사용하거나 주 스레드 함수로부터 반환해야 합니다. 왜냐하면 ExitThread를 사용하면 CRT 리소스가 제대로 해제되지 않기 때문입니다. 스레드가 제대로 정리되지 않으므로 TerminateThread 함수를 호출하지 마세요. 스레드는 항상 자살해야 하며, 절대로 살해되어서는 안 됩니다.
OpenMP
OpenMP는 pragmas를 사용하여 컴파일러를 병렬 처리 루프로 안내하여 프로그램에 다중 스레딩을 추가하기 위한 언어 확장입니다. OpenMP는 Windows 및 Xbox 360의 Visual C++ 2005에서 지원되며 수동 스레드 관리와 함께 사용할 수 있습니다. OpenMP는 코드의 다중 스레드 부분에 편리한 방법이 될 수 있지만, 특히 게임에서 이상적인 솔루션이 될 가능성은 낮습니다. OpenMP는 처리 아트 및 기타 리소스와 같은 장기 실행 프로덕션 작업에 더 많이 적용할 수 있습니다. 자세한 내용은 Visual C++ 설명서를 참조하거나 또는 OpenMP 웹 사이트을 방문하세요.
프로 파일링
다중 스레드 프로파일링이 중요합니다. 스레드가 서로 대기하는 긴 중단으로 쉽게 끝날 수 있습니다. 이러한 노점은 찾고 진단하기 어려울 수 있습니다. 이를 식별하려면 동기화 호출에 계측을 추가하는 것이 좋습니다. 샘플링 프로파일러를 사용하면 타이밍 정보를 크게 변경하지 않고도 기록할 수 있으므로 이러한 문제를 식별하는 데 도움이 될 수 있습니다.
타이밍
rdtsc 명령은 Windows에서 정확한 타이밍 정보를 가져오는 한 가지 방법입니다. 아쉽게도 rdtsc에는 배송 타이틀에 대한 잘못된 선택이 되는 여러 가지 문제가 있습니다. rdtsc 카운터가 CPU 간에 반드시 동기화되는 것은 아니므로 스레드가 하드웨어 스레드 간에 이동하면 큰 양수 또는 음의 차이가 발생할 수 있습니다. 전원 관리 설정에 따라 게임이 실행되면 rdtsc 카운터 증가 빈도도 변경됩니다. 이러한 어려움을 방지하려면 출시 게임에서 정밀한 타이밍을 위해 QueryPerformanceCounter 및 QueryPerformanceFrequency를 선호해야 합니다. 타이밍에 대한 자세한 내용은 게임 타이밍 및 멀티 코어 프로세서참조하세요.
디버깅
Visual Studio는 Windows 및 Xbox 360용 다중 스레드 디버깅을 완벽하게 지원합니다. Visual Studio 스레드 창을 사용하면 서로 다른 호출 스택과 지역 변수를 확인하기 위해 스레드 간에 전환할 수 있습니다. 스레드 창을 사용하면 특정 스레드를 고정하고 다시 해동할 수도 있습니다.
Xbox 360에서는 워치 윈도우에서 @hwthread 메타 변수를 사용하여 현재 선택한 소프트웨어 스레드가 실행 중인 하드웨어 스레드를 표시할 수 있습니다.
스레드의 이름을 의미 있는 이름으로 지정하면 스레드 창을 더 쉽게 사용할 수 있습니다. Visual Studio 및 기타 Microsoft 디버거를 사용하면 스레드 이름을 지정할 수 있습니다. 다음 SetThreadName 함수를 구현하고 시작 시 각 스레드에서 호출합니다.
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // must be 0x1000
LPCSTR szName; // pointer to name (in user address space)
DWORD dwThreadID; // thread ID (-1 = caller thread)
DWORD dwFlags; // reserved for future use, must be zero
} THREADNAME_INFO;
void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName )
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = szThreadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException( 0x406D1388, 0,
sizeof(info) / sizeof(DWORD),
(DWORD*)&info );
}
__except( EXCEPTION_CONTINUE_EXECUTION ) {
}
}
// Example usage:
SetThreadName(-1, "Main thread");
KD(커널 디버거) 및 WinDBG는 다중 스레드 디버깅도 지원합니다.
테스트
다중 스레드 프로그래밍은 까다로울 수 있으며 일부 다중 스레드 버그는 거의 표시되지 않으며 찾기 및 수정이 어렵습니다. 그들을 제거할 수 있는 가장 좋은 방법 중 하나는 다양한 컴퓨터, 특히 프로세서가 4개 이상인 컴퓨터에서 테스트하는 것입니다. 단일 스레드 컴퓨터에서 완벽하게 작동하는 다중 스레드 코드는 4개 프로세서 컴퓨터에서 즉시 실패할 수 있습니다. AMD 및 Intel CPU의 성능 및 타이밍 특성은 크게 다를 수 있으므로 두 공급업체의 CPU를 기반으로 하는 다중 프로세서 컴퓨터에서 테스트해야 합니다.
Windows Vista 및 Windows 7 개선 사항
최신 버전의 Windows를 대상으로 하는 게임의 경우 확장 가능한 다중 스레드 애플리케이션 만들기를 간소화할 수 있는 여러 API가 있습니다. 새 ThreadPool API 및 일부 추가 동기화 기본 형식(조건 변수, 슬림 읽기/기록기 잠금 및 일회성 초기화)에서는 특히 그렇습니다. 다음 MSDN Magazine 문서에서 이러한 기술에 대한 개요를 찾을 수 있습니다.
- 새 스레드 풀 API 사용하여 확장성 향상
- Windows Vista에 새로 추가된 동기화 원시 개체
이러한 운영 체제에서 Direct3D 11 기능을 사용하는 애플리케이션은 다중 스레드 렌더링의 확장성을 높이기 위해 동시 개체 만들기 및 지연된 컨텍스트 명령 목록을 위한 새로운 디자인을 활용할 수도 있습니다.
요약
스레드 간의 상호 작용을 최소화하는 신중한 디자인을 사용하면 코드에 과도한 복잡성을 추가하지 않고도 다중 스레드 프로그래밍에서 상당한 성능 향상을 얻을 수 있습니다. 이렇게 하면 게임 코드가 프로세서 개선의 다음 물결을 타고 더욱 매력적인 게임 환경을 제공할 수 있습니다.
참조
- 짐 베버리지 & 로버트 와이너 , Win32 멀티스레딩 애플리케이션, 애디슨-웨슬리, 1997
- Chuck Walbourn, 게임 타이밍 및 멀티 코어 프로세서, Microsoft Corporation, 2005
- MSDN 라이브러리: GetLogicalProcessorInformation
- OpenMP