#include <exception>
#include <future>
#include <iostream>
#include <string>
#include <thread>
using std::string;
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();
}
std::future은 std::promise에 data를 넣어주겠다는 약속과 같다. promise 객체에 다른 쓰레드가 데이터를 넣어줄 때 까지 std::future::wait()를 해서 기다릴 수 있고, std::future::get()을 통해 데이터를 가져올 수도 있다.
future::get()을 통해서 데이터를 읽으면 future 내부의 객체가 이동되기 때문에 다시는 접근할 수 없다.
예외 throw, catch
std::future은 예외도 받을 수 있다. std::future::get()을 하는 시점에 promise에 예외를 std::promise::set_exception(std::current_exception)을 통해서 보내면 예외를 catch할 수 있다. (참고 : std::current_exception은 예외 객체 자신이 아닌 exception의 포인터임을 명심할 것)
#include <future>
#include <iostream>
#include <string>
#include <thread>
using std::string;
void worker(std::promise<string>* p) {
// 약속을 이행하는 모습. 해당 결과는 future 에 들어간다.
p->set_value("some data");
}
int main() {
std::promise<string> p;
// 미래에 string 데이터를 돌려 주겠다는 약속.
std::future<string> data = p.get_future();
std::thread t(worker, &p);
// 미래에 약속된 데이터를 받을 때 까지 기다린다.
data.wait();
// wait 이 리턴했다는 뜻이 future 에 데이터가 준비되었다는 의미.
// 참고로 wait 없이 그냥 get 해도 wait 한 것과 같다.
std::cout << "받은 데이터 : " << data.get() << std::endl;
t.join();
}
wait_for
wait_for은 wait와는 다르게 지정된 시간만큼 대기하고 대기하는 중 promise에 데이터가 오지 않으면 그대로 리턴해버린다. 따라서, 1초정도 wait_for을 걸고 promise가 1초동안 오지 않는다면 바로 다음 코드가 실행된다고 이해하면 된다.
#include <chrono>
#include <thread>
#include <exception>
#include <iostream>
#include <string>
#include <future>
void worker(std::promise<void>* p) {
std::this_thread::sleep_for(std::chrono::seconds(10));
p->set_value();
}
int main() {
std::promise<void> p;
std::future<void> data = p.get_future();
std::thread t(worker, &p);
while (true) {
std::future_status status = data.wait_for(std::chrono::seconds(1));
if (status == std::future_status::timeout) {
std::cerr << ">";
}
else if (status == std::future_status::ready)
break;
}
t.join();
}
해당 메서드는 future_status 객체를 리턴한다. std::future_status::timeout은 객체가 준비되지 않았음을, std::future_status::ready는 해당 객체가 준비되었음을 의미한다.
shared_future
shared_future는 여러 쓰레드에서 get을 해도 promise 객체가 이동되지 않아 여러 쓰레드에서 데이터에 접근할 수 있다. 하나의 쓰레드에서 여러 쓰레드에 데이터를 보낼 때 용이하다.
#include <chrono>
#include <future>
#include <iostream>
#include <thread>
using std::thread;
void runner(std::shared_future<void> start) {
start.get();
std::cout << "출발!" << std::endl;
}
int main() {
std::promise<void> p;
std::shared_future<void> start = p.get_future();
thread t1(runner, start);
thread t2(runner, start);
thread t3(runner, start);
thread t4(runner, start);
// 참고로 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();
}
packaged_task
#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는 promise의 함수 버전같은 것이다. packaged_task에 넣은 함수 객체가 return하면 해당 return값을 promise에 넣어주고, 예외를 throw하면 예외를 전달한다. packaged_task는 복사 생성이 불가능하므로 thread를 생성할 때 std::move()를 활용해 rvalue임을 명시해주어야 한다.
std::async
std::packaged_task나 std::promise같은 경우 명시적으로 쓰레드를 생성해 주어야 했다. 하지만 std::async는 알아서 쓰레드를 생성해 준다.
#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 = std::async(std::launch::async, some_task, 5);
// std::future<int> start = std::async(std::async([]() { return some_task(5); });
// 람다 함수로 위와 같이 나타낼 수도 있음.
std::cout << "결과값 : " << start.get() << std::endl;
t.join();
}
async에 std::launch::async를 전달하면 객체가 생성되자마자 쓰레드를 생성해서 실행시키고, std::launch::deferred의 경우 future의 get 함수가 호출될 때 쓰레드를 생성한다.
async함수는 함수의 결과값을 포함하는 future을 리턴한다. 해당 값은 future.get을 통해서 얻어볼 수 있다.
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ Spinlock 구현 (0) | 2023.03.11 |
---|---|
C++ / std::atomic (0) | 2023.02.17 |
C++ 11 / mutex를 활용한 동기화 (0) | 2023.02.16 |
C++ 11 / 스레드 (0) | 2023.02.16 |
C++ / std::function (0) | 2023.02.16 |
댓글