
promise, future은 thread 간 단방향 전달 도구이다. 쉽게 말해 promise는 "값을 약속" 하고, future은 "값을 받는"역할을 수행한다.
#include <future>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
using namespace std;
void worker(promise<string>* p) {
// 시간 소비
this_thread::sleep_for(chrono::seconds(2));
// 약속을 이행
p->set_value("some data");
}
void example_function() {
// 1초 대기
this_thread::sleep_for(chrono::seconds(1));
cout << "This is an example function." << endl;
}
int main() {
promise<string> p;
future<string> data = p.get_future();
thread t(worker, &p);
example_function();
// 미래에 약속된 데이터를 받을 때 까지 기다린다.
data.wait();
// wait 이 리턴했다는 뜻이 future 에 데이터가 준비되었다는 의미.
// 참고로 wait 없이 그냥 get 해도 wait 한 것과 같다.
cout << "데이터 수신: " << data.get() << endl;
t.join();
std::condition_variable cv;
std::mutex m;
bool done = false;
std::string info;
void worker() {
{
std::lock_guard<std::mutex> lk(m);
info = "some data"; // 위의 p->set_value("some data") 에 대응
done = true;
}
cv.notify_all();
}
int main() {
std::thread t(worker);
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [] { return done; }); // 위의 data.wait() 이라 보면 된다.
lk.unlock();
std::cout << "받은 데이터 : " << info << std::endl;
t.join();
}
void worker(std::promise<string>* p) {
try {
throw std::runtime_error("Some Error!");
} catch (...) {
// set_exception 에는 exception_ptr 를 전달해야 한다.
p->set_exception(std::current_exception());
}
}
int main() {
std::promise<string> p;
// 미래에 string 데이터를 돌려 주겠다는 약속.
std::future<string> data = p.get_future();
std::thread t(worker, &p);
// 미래에 약속된 데이터를 받을 때 까지 기다린다.
data.wait();
try {
data.get();
} catch (const std::exception& e) {
std::cout << "예외 : " << e.what() << std::endl;
}
t.join();
}
set_exception을 통해 예외를 받으면 try-catch문을 이용해서 실제 예외를 처리할 수 있게 된다.wait_for을 사용하면 그냥 wait과 달리 특정 시간동안만 대기하고 아니면 그냥 진행하게 할 수 있다.
void worker(promise<string>* p) {
// 시간 소비
this_thread::sleep_for(chrono::seconds(5));
// 약속을 이행
p->set_value("some data");
}
void example_function() {
// 1초 대기
this_thread::sleep_for(chrono::seconds(1));
cout << "This is an example function." << endl;
}
int main() {
promise<string> p;
future<string> data = p.get_future();
thread t(worker, &p);
example_function();
while(true) {
future_status status = data.wait_for(chrono::seconds(1));
if (status == future_status::timeout) {
cout << "데이터 준비 중..." << endl;
} else if (status == future_status::ready) {
cout << "데이터 수신: " << data.get() << endl;
break;
}
}
t.join();
}
future_status는 총 3가지의 형태를 가질 수 있다. ready : future에 값이 설정 된 상태.timeout : wait_for의 시간이 지났지만 값이 설정되지 않은 상태.deferred : 아직 스레드가 시작되지 않았다는 뜻. 추후에 등장하는 개념으로 일반적인 promise, thread에서는 나오지 않는다.future의 경우에는 한번만 get할 수 있었는데, 그 이유는 future의 객체 내부가 이동하기 때문이다. 하지만 여러 스레드에서 future의 값을 여러번 get할 수 있게 해주는 것이 shared_future이다.
void runner(std::shared_future<void> start, int id) {
start.get();
printf("%d: 출발!\n", id);
}
int main() {
std::promise<void> p;
std::shared_future<void> start = p.get_future();
thread t1(runner, start, 1);
thread t2(runner, start, 2);
thread t3(runner, start, 3);
thread t4(runner, start, 4);
// 참고로 cerr 는 std::cout 과는 다르게 버퍼를 사용하지 않기 때문에 터미널에
// 바로 출력된다.
std::cerr << "준비...";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cerr << "땅!" << std::endl;
p.set_value();
t1.join();
t2.join();
t3.join();
t4.join();
}
특정 함수를 스레드에서 실핼할 수 있게 promise로 감싼 wrapper이다.

#include <future>
#include <iostream>
#include <thread>
int some_task(int x) { return 10 + x; }
int main() {
// int(int) : int 를 리턴하고 인자로 int 를 받는 함수. (std::function 참조)
std::packaged_task<int(int)> task(some_task);
std::future<int> start = task.get_future();
std::thread t(std::move(task), 5);
std::cout << "결과값 : " << start.get() << std::endl;
t.join();
}
packaged_task는 비동기적으로 수행할 함수 자체를 인자로 받는다. (감싼다.)packaged_task는 복사가 불가능하기 때문에 move를 통해서 소유권을 줘야 한다. (promise도 복사 불가능)Promise나 packaged_task를 사용하는 방법은 우리가 스레드를 별도로 만들어 줘야 했지만 async를 사용하면 자동으로 스레드를 만들어 비동기적으로 실행하고 결과를 future에 전달하게 된다.
int heavy_calc(int x) {
std::this_thread::sleep_for(2s); // 무거운 계산 흉내
return x * x;
}
promise<int> p;
future<int> f = p.get_future();
thread t([&p] { p.set_value(heavy_calc(5)); });
int result = f.get(); // 25
t.join();
packaged_task<int(int)> task(heavy_calc);
future<int> f = task.get_future();
thread t(std::move(task), 5);
int result = f.get(); // 25
t.join();
auto f = std::async(std::launch::async, heavy_calc, 5);
int result = f.get(); // 25 (자동 정리!)
std::launch::async : 새 스레드 만들어서 실행.std::launch::deferred : 지연 실행 -> future의 get함수가 호출되면 그 때 실행. (새 스레드 x)void do_work(int x) {
std::this_thread::sleep_for(std::chrono::seconds(x));
std::cout << "작업 끝남\n";
}
void do_work_parallel() {
auto f1 = std::async(std::launch::deferred, []() { do_work(3); });
auto f2 = std::async(std::launch::deferred,[]() { do_work(3); });
do_work(0);
f1.get();
f2.get();
}
int main() { do_work_parallel(); }
void do_work(int x) {
std::this_thread::sleep_for(std::chrono::seconds(x));
std::cout << "작업 끝남\n";
}
void do_work_parallel() {
auto f1 = std::async(std::launch::async, []() { do_work(1); });
auto f2 = std::async(std::launch::async,[]() { do_work(1); });
do_work(3);
f1.wait();
f2.wait();
}
int main() { do_work_parallel(); }
⚠️ std::async를 사용할때 launch는 생략하지 않는 것이 좋다. 생략하게 되면 구현체마다 다른 기본값으로 설정이 되어서 예측하기 어려워진다.