조금 활용도가 떨어지는것 같지만
c++11 에 공식적으로 추가된 친구니깐 내용은 알고 넘어가야한다.
시간이 오래걸린다면 비동기 방식으로 쓰레드에게 넘겨줘도 된다.
그러나 이런 한가지 함수 때문에 쓰레드를 생성하고 return 값을 담을 전역변수를 생성한다는게 뭔가 아쉽다.
약간 식당에서 1주일 일할 사람을 찾아야되는데 알바가 아닌 직원을 뽑은 느낌이다.
이때 알바 같은 역할을 std::future가 해준다. ( 언젠가 미래에 결과물을 뱉어 줄거야! )
future를 만들때 세가지 옵션이 있다
1) deferred => lazy evaluation 지연해서 실행하세요.
-> deferred를 넣어서 만들때는 멀티 쓰레드는 아니다.
그럼 이게 유용하냐? ㅇㅇ 커맨드 패턴의 전형적인 유형이다.
클라가 서버한테 어떤 좌표로 이동하라는 명령을 보내왔다고 해보자.
근데 서버가 바뻐서 당장 그걸 처리할 여력이 없다고 할때
그 요청상황을 future 를 통해 여력이 될때 처리 하게끔 할 때 유용하다.
결국엔 이것도 비동기 방식이라고 할 수 있다. ( 동기는 호출이 될 때 바로 실행되기 때문에 )
2) async => 별도의 쓰레드를 만들어서 실행하세요.
하나의 함수만 비동기로 이용하고 싶다 할때는 future 이용하면 유용하다.
예전에 강사님이 일하던 곳의 프로젝트에서
서버를 실행하면 xml 파일에서 몬스터의 체력, 행동 , 맵 이런걸 load 하고 시작했는데
게임이 오래되면서 xml 파일이 너무 커져서
future을 객체 몇개를 생성해서
어떤건 몬스터를 load 하고
어떤건 맵을 load 하고
이런식으로 만들었던 적이 있다.
물론 이걸 쓰레드를 새로 만들어서 해도 되지만
잠시 처음에 load 할때만 쓸거라, 쓰레드를 만들고 소멸하면서 관리하는게 더 부담될 수 도 있었다.
그리고 중간에 얘가 끝난앤지 아닌지 확인하고 싶을때는 future.status 를 확인해주면 된다.
future.wait_for() 함수를 실행하면 () 안의 시간 만큼 기다린 다음에 값을 반환해주길 기다리는데
이때 기다리는 도중에 값이 오면 std::future_status::ready 를 리턴해주고,
기다려도 값이 오지 않으면 std::future_status::time_out이 리턴된다.
이 상태를 가지고 일감이 끝났는지 아닌지 확인할 수 있다.
wait()은 사실상 waif_for(INFINITE) 느낌이다.
중요한거는 std::launch::async 옵션으로 std::future을 생성했다면 future 객체를 생성할떄부터 Calculate 함수를 싱행하게 되고
future.get() 을 하게 되면 만약 Calculate 함수가 끝났다면 결과물을 받아오고,
끝나지 않았다면 끝날때까지 기다려서 결과물을 받아온다.
std::launch::deferred 로 한다면 별도 쓰레드로 함수가 실행되지 않고 있다가
future.get() 을 호출했을때 Calculate 함수가 실행되면서 끝날때까지 기다리고 결과물을 받아온다.
future.wait()은 사실상 future.get()에 포함됐다고 봐도 된다.
future.wait()을 호출하고 future.get()을 호출하나 future.wait()을 생략하고 get()을 호출하나 다를게 없다.
#22.11.05(토) 추가
wait_for 함수가 궁금해서 몇가지 실험을 해봤다.
#22.11.05(토) 추가 끝
근데 일감을 줄때 전역함수나 static 함수가 아닌 멤버 함수를 주고 싶을때도 있을거다.
아래와 같이 말이다.
근데 멤버함수는 이렇게 future로 만들면 안된다.
이렇게 만들어줘야 한다.
그리고 future 라는게 std::async 를 통해서만 만들 수 있는것은 아니다.
다양한 방법으로 만 들 수 있다.
두번째 방법으로
std::promise 가 있다.
std::promise<string> promise;
std::future<string> future = promise.get_future();
무전기 처럼 1:1 대응이 되고,
제 3의 다른 쓰레드에서 promise 에다가 set 함수를 통해서 데이터를 set 해주면 get_future()를 통해 받아 올 수 있다.
실습을 해보자면
PromiseWorker라는 함수를 만들어주고
만약에 위 상태에서 다른 쓰레드를 만든다고 한다면
이렇게 된다면 현재까지 future는 main 쓰레드가 가지고 있고, promise는 만들어진 새로운 쓰레드 t가 소유권을 가지고 있다.
t가 PromiseWorker 함수 내의 set_value() 를 통해서 promise에 값을 넣어주게 되면 future.get() 을 통해 가지고 올 수 있는것이다.
* cout << message << endl; 해준다음에 t.join() 해줘야한다.
t.join() 을 30 line 전에서 해줘도 된다.
27 line 에서 breakPoint 걸어서 확인해 보면, promise나 futuere 둘다 pending 상태이다.
여기서 27번째라인을 f10을 통해 실행시켜보면,
std::move를 통해 promise 소유권 자체를 다른 쓰레드에게 넘겨줬기 때문에 promise는 empty 상태가 되고
main 쓰레드에서는 promise를 건드리면 안되는 상태가 됐다.
그다음에 30번째 줄을 실행시켜보면 ( future.get()을 빵 때리는 순간에 )
message에 데이터가 넘겨지게 되면서 future도 empty 상태가 된다.
이 말은 즉슨, 정말로 get을 통해 data를 한번 추출하는 순간 future 객체도 유요하지 않게 되기 때문에 혹시라도 get을 여러번 호출하게 되면 문제가 일어난다.( 딱 한번만 호출해야된다. )
정리해보자면
future를 만드는 첫번째 방법은
Calculate 라는 비동기 방식으로 실행 할 함수를 간단하게 만들어주고 결과물을 future 객체에서 get을 통해서 가지고 올 수 있는것이고,
두번째 방법에서는
promise 라는 어떻게 보면 future 를 세팅할 수 있는 창구를 만들어주고, 그 창구 자체를 다른 쓰레드한테 소유권을 넘겨줘서 개가 promise에 값을 세팅해주면 그걸 우리가 future 객체로 가지고 오는 방법이다.
그리고 한 가지 방법이 더 있다.
std::packaged_task<int64(void)> => < > 안에는 함수 타입이랑 맞혀줘야한다.
사실 promise 와 future의 관계랑
packaged_task 와 future의 관계랑 거의 비슷하다.
다만 promise 에서는 <> 타입에 맞는 데이터를 넣는 거고
packaged_task는 Calculate라는 것을 다른 쪽 쓰레드에서 호출해주세요 하면서 일감개념으로, 함수 타입을 <> 안에 넣어야된다.
* cout << sum << endl; 해준다음에 t.join() 해줘야한다.
3) deferred or async => 둘 중 알아서 골라주세요.
결론 : Event 나 condition_valriable 까지 가지 않고 단순한 애들을 처리 할 수 있는 방법을 알아본거다.
특히나 1회성을 한번만 일어나는 이벤트 들에 유용하다.
무한루프처럼 계속 일어나는게 아니라 한번만 일어난다면 future 객체를 통해서 받아오는게 유용하다.
물론 만드는 방법이 다르지만
- 어떤 함수를 그냥 비동기로 넘겨 줄것이냐. std::async
- 아니면 결과물 자체를 promise 자체에다가 받아 줄 것이냐
'C++과 언리얼로 만드는 게임 개발 > Part4. Server' 카테고리의 다른 글
★진짜 진짜 중요!!!★메모리 모델 | atomic 클래스/ 가시성 / 코드 재배치 (0) | 2022.06.08 |
---|---|
캐시 & CPU 파이프라인 ★★ 문제 제시 | V (0) | 2022.06.06 |
Event ( 커널 오브젝트, CreateEvent, WaitForSingleObject ) | V (0) | 2022.05.02 |
★엄청 중요★ Spin Lock ( feat. volatile, compare_exchange_strong() ) | V (0) | 2022.04.29 |
Lock 구현 이론 | V (0) | 2022.04.28 |