비동기 프로그래밍 (async, future)
비동기 프로그래밍의 개념
비동기 프로그래밍은 프로그램의 주 실행 흐름을 차단하지 않고 작업을 수행할 수 있게 해주는 프로그래밍 패러다임입니다.
C++ 11부터 도입된 <future> 헤더의 기능들을 통해 비동기 프로그래밍을 쉽게 구현할 수 있습니다.
std::future와 std::promise
std::future
는 비동기 작업의 결과를 나타내는 객체입니다.
std::promise
는 이 결과를 설정하는 역할을 합니다.
#include <iostream>
#include <future>
#include <thread>
void producer(std::promise<int>&& intPromise, int value) {
std::this_thread::sleep_for(std::chrono::seconds(1));
intPromise.set_value(value);
}
int main() {
std::promise<int> intPromise;
std::future<int> intFuture = intPromise.get_future();
std::thread t(producer, std::move(intPromise), 42);
std::cout << "Waiting..." << std::endl;
std::cout << "Result: " << intFuture.get() << std::endl;
t.join();
return 0;
}
std::async
std::async
는 비동기 작업을 더 쉽게 시작할 수 있게 해주는 함수입니다.
#include <iostream>
#include <future>
#include <thread>
int compute() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}
int main() {
std::cout << "Starting computation..." << std::endl;
std::future<int> result = std::async(std::launch::async, compute);
std::cout << "Doing other work..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
실행 정책
std::async
는 다음과 같은 실행 정책을 지원합니다.
std::launch::async
: 새로운 스레드에서 비동기적으로 실행std::launch::deferred
: 결과가 필요할 때까지 실행을 지연std::launch::async | std::launch::deferred
: 구현에 따라 선택
auto f1 = std::async(std::launch::async, compute);
auto f2 = std::async(std::launch::deferred, compute);
std::packaged_task
std::packaged_task
는 함수나 콜러블 객체를 future
와 연결합니다.
#include <iostream>
#include <future>
#include <thread>
int multiply(int a, int b) {
return a * b;
}
int main() {
std::packaged_task<int(int,int)> task(multiply);
std::future<int> result = task.get_future();
std::thread t(std::move(task), 2, 3);
std::cout << "Result: " << result.get() << std::endl;
t.join();
return 0;
}
예외 처리
비동기 작업에서 발생한 예외는 future
를 통해 전파됩니다.
#include <iostream>
#include <future>
#include <stdexcept>
void may_throw() {
throw std::runtime_error("Error in async operation");
}
int main() {
auto f = std::async(std::launch::async, may_throw);
try {
f.get();
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
여러 future 대기
std::future
의 wait_for
나 wait_until
메서드를 사용하여 여러 비동기 작업을 동시에 대기할 수 있습니다.
#include <iostream>
#include <future>
#include <vector>
#include <chrono>
int work(int id) {
std::this_thread::sleep_for(std::chrono::seconds(id));
return id;
}
int main() {
std::vector<std::future<int>> futures;
for (int i = 1; i <= 5; ++i) {
futures.push_back(std::async(std::launch::async, work, i));
}
bool all_done = false;
while (!all_done) {
all_done = true;
for (auto& f : futures) {
if (f.wait_for(std::chrono::seconds(0)) != std::future_status::ready) {
all_done = false;
break;
}
}
std::cout << "Waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
for (auto& f : futures) {
std::cout << "Result: " << f.get() << std::endl;
}
return 0;
}
연습 문제
std::async
를 사용하여 피보나치 수열의 n번째 항을 계산하는 프로그램을 작성하세요. 여러 개의 비동기 작업을 생성하여 계산을 병렬화하세요.std::promise
와std::future
를 사용하여 생산자-소비자 패턴을 구현하세요. 생산자는 데이터를 생성하고, 소비자는 이를 처리합니다.- 비동기 작업 체인을 구현하세요. 첫 번째 작업의 결과를 입력으로 받아 두 번째 작업을 수행하고, 그 결과를 다시 세 번째 작업의 입력으로 사용하는 방식으로 연결된 비동기 작업들을 만드세요.
참고자료
- C++ Concurrency in Action (2nd Edition) by Anthony Williams
- Effective Modern C++ by Scott Meyers (특히 Item 35-40)
- C++ 표준 문서의 <future> 헤더 관련 섹션
- "C++ Concurrency in Action" by Anthony Williams
- CppCon 발표 영상들 - 비동기 프로그래밍 관련 세션들
- "Concurrency with Modern C++" by Rainer Grimm