LoadScene() {
Render(); // 로딩 중 애니메이션 렌더링
LoadScene(); // 게임 장면 로드
Render();
LoadModel(); // 게임 모델 로드
Render();
LoadTexture(); // 게임 텍스쳐 로드
Render();
LoadAnimation();// 게임 애니메이션 로드
Render();
LoadSound(); // 게임 사운드 로드..
}
bool isStillLoading; // 전역 변수(공유)
Thread1 {
isStillLoading = true;
while(isStillLoading) {
FrameMove();
Render();
}
}
Thread2 {
LoadScene(); // 게임 장면 로드
LoadModel(); // 게임 모델 로드
LoadTexture(); // 게임 텍스쳐 로드
LoadAnimation();// 게임 애니메이션 로드
LoadSound(); // 게임 사운드 로드..
}
source: https://velog.io/@gojaegaebal/210309-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%8092%EC%9D%BC%EC%B0%A8-%EC%A0%95%EA%B8%80-%EB%82%98%EB%A7%8C%EC%9D%98-%EB%AC%B4%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Nodejs%EC%9D%98-%ED%8A%B9%EC%A7%95-%EB%B0%8F-%EC%9E%A5%EC%A0%90
서버의 코어가 여러 개일 때 싱글 쓰레드 프로그램으로 작성하며 작업의 부하를 분산시키지 않은 것은 바보같은 짓이다. 병렬적으로 수행할 수 있는 쓰레드 단위를 나눌 수 있을 땐 멀티쓰레드 프로그래밍을 통해 전반적인 성능을 높인다.
#include <vector>
#include <iostream>
#include <chrono>
using namespace std;
const int MaxCount = 1500000;
bool isPrimeNum (int num){
if(num == 1)
return false;
if(num == 2 || num == 3)
return true;
for(int i=2; i*i <= num; i++){
if(num % i == 0)
return false;
}
return true;
}
void PrintNumbers(const vector<int>& primes) {
for(int v : primes)
cout << v << endl;
}
int main(){
vector<int> primes;
// 시간측정용
auto t0 = chrono::system_clock::now();
for(int i=1; i<=MaxCount; i++){
if(isPrimeNum(i))
primes.push_back(i);
}
auto t1 = chrono::system_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(t1-t0).count();
PrintNumbers(primes);
cout << "소수 갯수: " << primes.size() << endl;
cout << "경과 시간: " << duration << "ms" << endl;
return 0;
}
=> 소수 갯수: 114155
=> 경과 시간: 312ms
만약 4코어 CPU에서 위 코드의 프로그램을 실행한다면, 하나의 코어만을 사용하는 셈이다.
이를 멀티쓰레드 프로그래밍을 적용하여 모든 코어를 적절히 사용하도록 한다.
#include <vector>
#include <iostream>
#include <chrono>
#include <thread>
#include <memory>
using namespace std;
#define _DEBUG
const int MaxCount = 1500000;
const int ThreadCount = 4;
// 각 쓰레드가 값을 꺼내는 곳
int num = 1;
vector<int> primes;
bool isPrimeNum(int num) {
if (num == 1)
return false;
if (num == 2 || num == 3)
return true;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0)
return false;
}
return true;
}
void PrintNumbers(const vector<int>& primes) {
for (int v : primes)
cout << v << endl;
}
void threadJob() {
while (true) {
int n;
n = num;
num++;
if (n >= MaxCount)
break;
if (isPrimeNum(n))
primes.push_back(n);
}
}
int main() {
auto t0 = chrono::system_clock::now();
// 작동할 워커 쓰레드
vector<thread*> threads;
for (int i = 0; i < ThreadCount; i++) {
thread* tptr = new thread(threadJob);
threads.push_back(tptr);
}
// 모든 쓰레드가 일을 마칠 때까지 기다림
for (int i = 0; i < ThreadCount; i++) {
threads[i]->join();
}
auto t1 = chrono::system_clock::now();
auto duration = chrono::duration_cast<chrono::milliseconds>(t1 - t0).count();
PrintNumbers(primes);
cout << "소수 갯수: " << primes.size() << endl;
cout << "경과 시간: " << duration << "ms" << endl;
// 모든 쓰레드 해제
for (int i = 0; i < ThreadCount; i++) {
delete threads[i];
}
return 0;
}
=> 하지만 위 코드는 에러가 발생한다..
문맥 교환을 하는 과정에서 적지 않은 양의 연산이 발생.
1) 실행 중이던 쓰레드의 상태를 어딘가에 저장
2) 과거에 실행하다가만 다른 쓰레드 중 하나를 고름
3) 고른 쓰레드의 상태를 복원
4) 다음 실행하던 지점으로 강제 이동
CPU가 2개고 쓰레드가 2개면 이론적으로는 문맥 교환을 전혀 할 필요가 없음
두 쓰레드가 공용 데이터에 접근해서 그 데이터 상태를 예측할 수 없게 하는 것을 경쟁 상태 또는 데이터 레이스(data race)라고 한다. 이는 가끔 부정확하게 연산 결과가 나오게 하는 원인이다.
이는 경쟁 구간 또는 임계 영역을 적절히 설정하여, 락(lock) 또는 조건 변수(condition variable) 기법을 사용하여 상호 배제(mutual exclusion)을 보장해주면 된다.
뮤텍스가 보호하는 영역이 너무 넓으면 쓰레드가 여럿이라 하더라도 하나일 때와 별반 차이가 없다.
여러 CPU가 각 쓰레드의 연산을 실행하여 동시에 처리량을 올리는 것을 병렬성(parallelism)이라고 한다. 그런데 어떤 이유로 이러한 병렬성이 제대로 나오지 않는 것, 즉 병렬로 실행되게 프로그램을 만들었는데 정작 한 CPU만 연산을 수행하는 현상을 시리얼 병목(serial bottleneck)이라고 한다.