C++ async & launch | std::async·future·launch 정책 완벽 정리
이 글의 핵심
std::async는 함수를 비동기로 실행하고 future로 결과를 받는 C++11 API입니다. launch::async, launch::deferred 정책과 실전 예제를 정리합니다.
들어가며
std::async는 함수를 비동기로 실행하고 std::future로 결과를 받는 C++11 API입니다. std::thread보다 간결하며, launch 정책으로 실행 시점을 제어할 수 있습니다.
이 글을 읽으면
std::async로 비동기 작업을 실행하고 결과를 받습니다launch::async,launch::deferred정책의 차이를 이해합니다- 병렬 작업, 타임아웃, 예외 처리 패턴을 익힙니다
- 실무에서 자주 쓰이는 비동기 패턴을 구현합니다
목차
기본 개념
std::async란?
std::async는 함수를 비동기로 실행하고 std::future로 결과를 받습니다.
#include <future>
#include <iostream>
int compute(int x) {
return x * x;
}
int main() {
auto future = std::async(compute, 10);
int result = future.get(); // 결과 대기
std::cout << "결과: " << result << std::endl; // 100
return 0;
}
launch 정책
| 정책 | 실행 시점 | 스레드 생성 | 사용 시나리오 |
|---|---|---|---|
| launch::async | 즉시 | ✅ 새 스레드 | CPU 집약적 작업 |
| launch::deferred | get() 호출 시 | ❌ 현재 스레드 | 조건부 실행 |
| async | deferred | 구현 의존 | 자동 선택 | 일반적 사용 (기본) |
실전 구현
1) 기본 사용
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int compute(int x) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x;
}
int main() {
auto future = std::async(compute, 10);
std::cout << "계산 중..." << std::endl;
int result = future.get(); // 대기
std::cout << "결과: " << result << std::endl; // 100
return 0;
}
2) launch::async - 즉시 실행
#include <future>
#include <iostream>
#include <thread>
int main() {
auto future = std::async(std::launch::async, []() {
std::cout << "비동기 스레드 ID: "
<< std::this_thread::get_id() << std::endl;
return 42;
});
std::cout << "메인 스레드 ID: "
<< std::this_thread::get_id() << std::endl;
int result = future.get();
std::cout << "결과: " << result << std::endl;
return 0;
}
출력:
메인 스레드 ID: 140735268771840
비동기 스레드 ID: 123145307557888
결과: 42
3) launch::deferred - 지연 실행
#include <future>
#include <iostream>
#include <thread>
int main() {
auto future = std::async(std::launch::deferred, []() {
std::cout << "지연 실행 스레드 ID: "
<< std::this_thread::get_id() << std::endl;
return 42;
});
std::cout << "메인 스레드 ID: "
<< std::this_thread::get_id() << std::endl;
std::cout << "get 호출 전" << std::endl;
int result = future.get(); // 이때 실행
std::cout << "결과: " << result << std::endl;
return 0;
}
출력:
메인 스레드 ID: 140735268771840
get 호출 전
지연 실행 스레드 ID: 140735268771840
결과: 42
주의: 같은 스레드에서 실행됨
4) 여러 비동기 작업
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int compute1() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 10;
}
int compute2() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 20;
}
int compute3() {
std::this_thread::sleep_for(std::chrono::seconds(1));
return 30;
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
auto f1 = std::async(std::launch::async, compute1);
auto f2 = std::async(std::launch::async, compute2);
auto f3 = std::async(std::launch::async, compute3);
int total = f1.get() + f2.get() + f3.get();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "총합: " << total << std::endl; // 60
std::cout << "시간: " << duration << "초" << std::endl; // 1초
return 0;
}
성능: 순차 실행 3초 → 병렬 실행 1초 (3배 개선)
5) 예외 처리
#include <future>
#include <iostream>
#include <stdexcept>
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("0으로 나눌 수 없음");
}
return a / b;
}
int main() {
auto future = std::async(divide, 10, 0);
try {
int result = future.get(); // 예외 재던지기
} catch (const std::exception& e) {
std::cout << "예외: " << e.what() << std::endl;
}
return 0;
}
6) future 상태 확인
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int longCompute() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
}
int main() {
auto future = std::async(std::launch::async, longCompute);
// 타임아웃
auto status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
std::cout << "완료" << std::endl;
} else if (status == std::future_status::timeout) {
std::cout << "타임아웃 (아직 실행 중)" << std::endl;
}
// 완료 대기
future.wait();
std::cout << "결과: " << future.get() << std::endl;
return 0;
}
고급 활용
1) 병렬 다운로드
#include <future>
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
std::string downloadFile(const std::string& url) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return "Data from " + url;
}
int main() {
std::vector<std::string> urls = {
"http://example.com/file1",
"http://example.com/file2",
"http://example.com/file3"
};
std::vector<std::future<std::string>> futures;
for (const auto& url : urls) {
futures.push_back(std::async(std::launch::async, downloadFile, url));
}
for (auto& future : futures) {
std::cout << future.get() << std::endl;
}
return 0;
}
2) 병렬 맵 리듀스
#include <future>
#include <vector>
#include <numeric>
#include <iostream>
int sum_range(const std::vector<int>& data, size_t start, size_t end) {
return std::accumulate(data.begin() + start, data.begin() + end, 0);
}
int parallel_sum(const std::vector<int>& data, size_t num_threads) {
size_t chunk_size = data.size() / num_threads;
std::vector<std::future<int>> futures;
for (size_t i = 0; i < num_threads; ++i) {
size_t start = i * chunk_size;
size_t end = (i == num_threads - 1) ? data.size() : (i + 1) * chunk_size;
futures.push_back(std::async(std::launch::async, sum_range,
std::ref(data), start, end));
}
int total = 0;
for (auto& future : futures) {
total += future.get();
}
return total;
}
int main() {
std::vector<int> data(10000000, 1);
int sum = parallel_sum(data, 4);
std::cout << "합계: " << sum << std::endl; // 10000000
return 0;
}
3) 타임아웃 패턴
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int slowCompute() {
std::this_thread::sleep_for(std::chrono::seconds(5));
return 42;
}
int main() {
auto future = std::async(std::launch::async, slowCompute);
auto status = future.wait_for(std::chrono::seconds(2));
if (status == std::future_status::ready) {
std::cout << "결과: " << future.get() << std::endl;
} else {
std::cout << "타임아웃: 기본값 사용" << std::endl;
// future는 계속 실행 중 (취소 불가)
// 기본값 반환 또는 다른 처리
}
return 0;
}
성능 비교
async vs thread
테스트: 간단한 계산 작업
| 방식 | 코드 복잡도 | 결과 반환 | 예외 처리 | 오버헤드 |
|---|---|---|---|---|
| std::async | 낮음 | future | 자동 | 중간 |
| std::thread | 높음 | 수동 (공유 변수) | 수동 | 낮음 |
병렬 실행 벤치마크
테스트: 3개 작업, 각 1초
| 실행 방식 | 시간 | 배속 |
|---|---|---|
| 순차 실행 | 3초 | 1x |
| 병렬 실행 (async) | 1초 | 3x |
결론: 병렬 실행으로 3배 개선
launch 정책 비교
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int compute() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 42;
}
int main() {
// launch::async
auto start1 = std::chrono::high_resolution_clock::now();
auto f1 = std::async(std::launch::async, compute);
auto end1 = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 - start1).count();
std::cout << "async 생성: " << duration1 << "us" << std::endl;
// 약 50us (스레드 생성 비용)
// launch::deferred
auto start2 = std::chrono::high_resolution_clock::now();
auto f2 = std::async(std::launch::deferred, compute);
auto end2 = std::chrono::high_resolution_clock::now();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 - start2).count();
std::cout << "deferred 생성: " << duration2 << "us" << std::endl;
// 약 1us (지연 실행)
f1.get();
f2.get();
return 0;
}
실무 사례
사례 1: 웹 서버 - 병렬 요청 처리
#include <future>
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
struct Request {
std::string url;
std::string method;
};
std::string processRequest(const Request& req) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return "Response from " + req.url;
}
int main() {
std::vector<Request> requests = {
{"http://api.example.com/users", "GET"},
{"http://api.example.com/posts", "GET"},
{"http://api.example.com/comments", "GET"}
};
std::vector<std::future<std::string>> futures;
for (const auto& req : requests) {
futures.push_back(std::async(std::launch::async, processRequest, req));
}
for (auto& future : futures) {
std::cout << future.get() << std::endl;
}
return 0;
}
사례 2: 데이터 처리 - 병렬 파일 읽기
#include <future>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>
std::string readFile(const std::string& filename) {
std::ifstream file(filename);
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return content;
}
int main() {
std::vector<std::string> filenames = {
"file1.txt",
"file2.txt",
"file3.txt"
};
std::vector<std::future<std::string>> futures;
for (const auto& filename : filenames) {
futures.push_back(std::async(std::launch::async, readFile, filename));
}
for (size_t i = 0; i < futures.size(); ++i) {
try {
std::string content = futures[i].get();
std::cout << "파일 " << i << " 크기: " << content.size() << std::endl;
} catch (const std::exception& e) {
std::cout << "파일 " << i << " 읽기 실패: " << e.what() << std::endl;
}
}
return 0;
}
사례 3: 게임 - 비동기 리소스 로딩
#include <future>
#include <vector>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
struct Texture {
std::string name;
int width, height;
};
Texture loadTexture(const std::string& filename) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
return {filename, 1024, 1024};
}
int main() {
std::vector<std::string> textures = {
"player.png",
"enemy.png",
"background.png"
};
std::vector<std::future<Texture>> futures;
for (const auto& filename : textures) {
futures.push_back(std::async(std::launch::async, loadTexture, filename));
}
std::cout << "로딩 중..." << std::endl;
std::vector<Texture> loadedTextures;
for (auto& future : futures) {
loadedTextures.push_back(future.get());
}
std::cout << "로딩 완료: " << loadedTextures.size() << "개" << std::endl;
return 0;
}
트러블슈팅
문제 1: future 소멸자 블로킹
증상: 프로그램이 예상치 않게 대기
// ❌ future 무시
std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(5));
}); // 소멸자에서 대기 (블로킹)
std::cout << "다음 작업" << std::endl; // 5초 후 출력
// ✅ future 저장
auto future = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(5));
});
std::cout << "다음 작업" << std::endl; // 즉시 출력
future.get(); // 명시적 대기
문제 2: get 여러 번 호출
증상: std::future_error 예외
auto future = std::async([]() { return 42; });
int r1 = future.get(); // OK
// int r2 = future.get(); // 에러: future_error
// ✅ get은 한 번만
// 여러 스레드에서 공유하려면 shared_future 사용
문제 3: 데드락
증상: 프로그램이 멈춤
#include <future>
#include <mutex>
std::mutex mtx;
// ❌ 데드락 가능
void bad_pattern() {
std::lock_guard<std::mutex> lock(mtx);
auto future = std::async(std::launch::async, []() {
std::lock_guard<std::mutex> lock(mtx); // 데드락
return 42;
});
future.get();
}
// ✅ 락 범위 최소화
void good_pattern() {
auto future = std::async(std::launch::async, []() {
std::lock_guard<std::mutex> lock(mtx);
return 42;
});
future.get();
}
문제 4: 작은 작업의 오버헤드
증상: 병렬 실행이 오히려 느림
// ❌ 작은 작업 (스레드 생성 비용 > 작업 시간)
auto future = std::async(std::launch::async, []() {
return 1 + 1; // 너무 간단
});
// ✅ 충분히 큰 작업만 병렬화
auto future = std::async(std::launch::async, []() {
// 복잡한 계산 (수백 ms 이상)
return compute_heavy();
});
기준: 작업 시간 > 스레드 생성 비용 (약 50us)
마무리
std::async는 비동기 작업을 간결하게 표현하고 std::future로 결과를 받을 수 있게 합니다.
핵심 요약
-
기본 사용
std::async(func, args...)로 비동기 실행future.get()으로 결과 대기
-
launch 정책
launch::async: 즉시 실행 (새 스레드)launch::deferred: 지연 실행 (get 호출 시)- 기본:
async | deferred(구현 의존)
-
예외 처리
- 예외는
get()호출 시 재던지기 try-catch로 처리
- 예외는
-
주의사항
- future 소멸자는 블로킹
get()은 한 번만 호출 가능- 작은 작업은 오버헤드 주의
선택 가이드
| 상황 | 방법 |
|---|---|
| 간단한 비동기 작업 | std::async |
| 결과 반환 필요 | std::async + future.get() |
| 여러 스레드에서 결과 공유 | shared_future |
| 세밀한 스레드 제어 | std::thread |
코드 예제 치트시트
// 기본 사용
auto future = std::async(func, args...);
int result = future.get();
// launch::async (즉시 실행)
auto f1 = std::async(std::launch::async, func);
// launch::deferred (지연 실행)
auto f2 = std::async(std::launch::deferred, func);
// 타임아웃
auto status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
// 완료
}
// 예외 처리
try {
future.get();
} catch (const std::exception& e) {
// 처리
}
다음 단계
- future와 promise: C++ future와 promise
- shared_future: C++ shared_future
- packaged_task: C++ packaged_task
참고 자료
- “C++ Concurrency in Action” - Anthony Williams
- “Effective Modern C++” - Scott Meyers
- cppreference: https://en.cppreference.com/w/cpp/thread/async
한 줄 정리: std::async는 비동기 작업을 간결하게 표현하며, launch 정책으로 실행 시점을 제어하고 future로 결과를 안전하게 받는다.