서론
게임 서버 프로젝트에서 로직 스레드를 싱글로 변환했다. 로직스레드가 싱글로 돌아가기 때문에 I/O에서 블로킹이 일어나면 그만큼 처리의 레이턴시가 생기는 것이기 때문에 이를 개선하기 위해 데이터베이스 쿼리를 비동기로 실행하고, 완료 콜백을 호출할 시스템을 구현하기로 했다.
본격 비동기화 시키기
따로 데이터베이스 쿼리를 처리할 스레드를 생성할까 했지만, 스레드와 스레드가 데이터를 주고받는것 자체가 지연이 생길 수 밖에 없어 좋지 않아 보였다. 그렇다고 각 쿼리마다 스레드를 파는 것은 너무 효율이 떨어지는 방법이다.
가장 단순한 방법으로 task를 생각했다. task는 promise, future, packaged_task, async 등을 사용해 개발하는 것이다.
task의 구현 자체는 스레드의 wrapper이므로 느릴 수 밖에 없지만, 가독성 측면에서 월등히 좋다.
하지만 문제가 있다. cppreference에 따르면 std::async의 비동기적 실행은 potentially in a separate thread which might be a part of a thread pool, 즉 task에 대해 매번 새로운 스레드를 생성하지 않다고 된다는 것이다. 하지만 실제로는 GNU의 libstdc++의 경우, stackoverflow의 한 유저가 실험을 해본 후 올린 질문이 있다.
이 질문에 대한 답변의 요약은 다음과 같다.
- 현재 libstdc++ 구현은 std::async는 각각의 task에 새로운 스레드가 필요하지 않다는 사실의 이점을 취하지 않습니다.
- 현재 libstdc++ 구현은 std::async는 std::thread가 수행하지 않는 일종의 잠금(lock)을 수행합니다.
- std::async와 std::launch::deferred는 설정과 파괴 비용을 절약합니다.
답변자의 말에 따르면 std::async는 매번 호출될 때마다 새로운 스레드를 생성하고, 오히려 더 많은 스레드를 생성한다는 것이다. 만약 정말 그렇다면 비동기 호출의 결과를 받아온다는 점 말고는 std::thread를 사용하는 편이 더 효율적이라는 것이다.
결론
인터넷에 널려있는 블로그를 보면 스레드를 풀링한다고 하는데 맞을 수도 있고 틀릴수도 있다. 결국 STL의 표준은 호출 인터페이스와 같은 것만 정해져 있기 때문에 내부 구현은 GNU나 Microsoft, Apple각각 모두 다를 수 있다. 그러므로 std::async이 풀링을 사용할 수도, 안할 수도 있다는 것이다.
std::async를 사용하는것은 이러한 점을 숙지하고 프로젝트에 알맞은 방향으로 사용하도록 하자.
'C++' 카테고리의 다른 글
[C++] 메모리 뷰에서의 다중 상속, 캐스팅 (0) | 2024.08.15 |
---|---|
[C++] vcpkg 라이브러리 패키징 (0) | 2024.08.07 |
[C++] C++에서 라이브러리를 관리해보자 (0) | 2024.06.27 |
[C++/Unreal] 커스텀 RPC 구현 기록 (0) | 2024.05.31 |
[C++] 에러코드를 메세지로 format하기 (0) | 2023.11.17 |