C++ 비동기 처리

정은성·2023년 3월 16일
2
post-thumbnail

※ Rookiss님의 [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 강의를 보고 정리한 글입니다.

동기 (synchronous) 와 비동기 (asynchronous)

  • 동기 실행 : 코드를 실행하며 하나가 끝나면 다음 코드가 실행되는 방식
  • 비동기적 처리 : 코드 진행이 여러 갈래로 동시에 진행되는 것

비동기 처리를 해주는 것들이 바로 Future, Promise, packaged_task다!

Future

Calculate라는 함수가 있다.

int Calculate() {
	int sum = 0;

	for (int32 i = 0; i < 100'000; i++)
		sum += i;

	result = sum;
	return sum;
}

이 Calculate라는 함수는 동기 실행으로 진행하게되면 오랜 시간을 소모한다. 이 함수를 Future를 통해 비동기로 실행해보자

std::future<int> future = std::async(std::launch::async/*타입*/, Calculate/*함수 이름*/);
int sum = future.get(); // 결과 값을 받아온다.

future의 타입엔 2가지가 있다.

타입 종류
1) deferred
지연해서 실행하세요. → 나중에 실행하고자 할 때 실행한다.
⇒ 왜쓰지? 나중에 커맨드 패턴 처럼 나중에 실행을 해야할 때 사용된다.
2) async
별도의 쓰레드를 만들어 함수를 실행 한다.

상태 체크

future_status status = future.wait_for(1ms);// 원하는 만큼 대기
future.wait(); // 작업이 끝날 때 까지 대기
if (status == future_status::ready) {
	//TODO
}

wait_for을 사용하면 status를 얻을 수 있다!

멤버 함수에 사용

클래스 하위에있는 함수는 어떤 식으로 써야할까?

class Knight {
public:
	int GetHp() { return 100; }
};

Knight knight;

std::future<int> future2 = std::async(std::launch::async, &Knight::GetHp, knight);

멤버함수의 주소, 함수를 쓸 객체를 넣어주면된다.

Promise

미래(std::future)에 결과물을 반환해준다고 약속(std::promise)하는 것

여러 쓰레드 끼리 값을 주고 받을 때 관리하기 힘든 전역 변수같은 것이 아닌 Promise를 사용한다.

std::promise<string> promise;
std::future<string> future = promise.get_future();

promise에서 그에 맞는 future를 받을 수 있다.

계약서같은 것이라고 생각하면 된다. 계약서는 나만 가지고있는 것이아닌 나와 상대방 모두 가지고 있다!

이제 상대방에게 계약서를 넘겨준다.

void PromiseWorker(std::promise<string>&& promise) {
	promise.set_value("Secret Message"); // setValue!
}

thread t(PromiseWorker, std::move(promise)); // 오른 값으로 바꿔 소유권을 내가 아닌 상대방으로 바꿔준다!

t.join();

promise의 set_value를 사용하게 되면 promise에서 뽑아왔던 future에서 이를 받을 수 있다. 이것을 이용해 쓰레드 끼리 데이터를 주고받고할 수 있다!

string message = future.get();
cout << message << endl;

future.get은 한 번 호출하면 future도 더 이상 유효하지않기 때문에 중복해서 사용하는 것을 주의해야 한다!

Packaged_task

pakaged_task는 promise와 유사하다.

선언

std::packaged_task<int(void) /*함수와 인터페이스 맞추기*/> task(Calculate);
std::future<int> future = task.get_future(); // 마찬가지로 future 가져오기

사용

void TaskWorker(std::packaged_task<int(void)>&& task) {
	task(); // 그냥 받아온 함수 바로 실행
}

std::thread t(TaskWorker, std::move(task)); // 오른값으로 넘겨주기

int sum = future.get();
cout << sum << endl;

t.join();

task에 있는 함수의 반환값을 future을 통해 얻을 수 있게 된다.

그럼 이건 그냥 future를 사용하는 것이랑 뭐가달라?!

future같은 경우 future에게 넘겨주는 오직 그 일감을 위해 새로운 스레드를 사용해 비동기로 실행하게된다.

하지만 packaged_task의 경우 이미 생성 되어있는 스레드에게 일감을 던져줄 수 있다!

미묘한 차이가 있는것이다.

결론

mutex, condition_variable까지 가지않고 단순한 애들을 처리할 수 있다.
특히, 한 번 발생하는 단발성 이벤트에 유용하다.

1) async
원하는 함수를 비동기적으로 실행

2) promise
결과물을 promise를 통해 future로 받아줌

3) packaged_task
원하는 함수의 실행 결과를 packaged_task를 통해 future로 받아줌

1개의 댓글

comment-user-thumbnail
2023년 3월 16일

NICE WORK

답글 달기