《출처. 시작하자! C++17 프로그래밍 (박헌재 지음)》
시작하기 전 동기와 비동기에 대해서 먼저 알아보자!
Asynchronous(비동기) Synchronous(동기) 발음도 어려워 보이는 동기, 비동기 일단 말은 할 수 있어야 하니.. 번역기에 돌려 읽어주는데로 한 번 적어보겠습니다.
Synchronous-> siNGkrənəs(씨-인!크로너스)
Asynchronous->āˈsiNGkrənəs(에이 씨-인!크로너스)
이런식으로 발음해주고 있습니다. 알아 둡시다.. 면접에서 물어보면 알아듣긴 해야하니깐 (T_T)/
이제 이 둘의 차이점과 지닌 뜻을 알아보자!
결국 비동기적 실행은 하고 싶은 일을, 어떠한 데이터를 다른 쓰레드를 통해 처리해서 받아내는 과정이다.
c++에서 비동기 프로그래밍을 도와주는 클래스와 함수를 알아보는 과정을 공부하고 응용해보자.
● future 클래스 : "자체적으로 객체를 생성시키는 일반 생성자를 제공하지 않는다."
furture 객체를 반환하는 async() 함수는 두 종류를 제공한다.
- async ( Function&& f, Args&& ... args ) : <futrue>
- async ( std::lauch policy, Function&& f, Args&&... args ) : <future>
async()함수에서 사용하는 인수 rvalue 참조를 사용한다.
요즘 스레드보다 선호되며, std::future객체로 리턴을 한다. 리턴 값은 현재 받지 못하는 값 일수도 있음을 미래에 받을 수도 있음을 염두해두고 기다린다. 리턴값을 받으면 future.get()을 수행한다.
스레드버전과 async함수 사용버전으로 나눠서 보자!
코드 참조는 시작하자! C++17 프로그래밍 도서를 통해 코드를 작성하였다.
#include <thread>
#include <iostream>
#include <chrono>
#include <numeric>
#include <vector>
std::vector<std::thread> workers(std::vector<int>& , std::vector<int>*);
void accumulate_block_worker(int*, size_t, int*);
int main(void)
{
std::vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
std::vector<int> result(2, 0);
std::vector<std::thread> threads = workers(v, &result);
for (auto& t : threads)
{
t.join();
}
std::cout << "각각 배열의 합은" << result[0] << "과" << result[1] << std::endl;
std::cout << "두 개 배열의 합은" << result[0] + result[1] << std::endl;
return 0;
}
std::vector<std::thread> workers(std::vector<int>& v, std::vector<int>* result)
{
std::vector<std::thread> threads;
threads.emplace_back(accumulate_block_worker, v.data(), v.size() / 2, &((*result)[0]));
threads.emplace_back(accumulate_block_worker, v.data(), v.size() / 2, &((*result)[1]));
return threads;
}
void accumulate_block_worker(int* data, size_t count, int* result)
{
*result = std::accumulate(data, data + count, 0); //#include <numeric>
}
#include <iostream>
#include <chrono>
#include <numeric>
#include <vector>
#include <future>
int accumulate_block_worker(int*, size_t);
std::vector<std::future<int>> launch_split_workers(std::vector<int>&);
int main(void)
{
std::vector<int> v{ 1,2,3,4,5,6,7,8,9,10 };
std::vector<std::future<int>> futures = launch_split_workers(v);
int result0 = futures[0].get();
int result1 = futures[1].get();
std::cout << "각각 배열의 합은 " << result0 << "과" << result1 << std::endl;
std::cout << "두 개 배열의 합은 " << result0 + result1 << std::endl;
}
int accumulate_block_worker(int* data, size_t count)
{
return std::accumulate(data, data + count, 0); //#include <numeric>
}
std::vector<std::future<int>> launch_split_workers(std::vector<int>&v)
{
std::vector<std::future<int>> futures;
//비동기 스레드 객체를 생성하여 벡터 객체에 입력한다.
futures.emplace_back(std::async(std::launch::async, accumulate_block_worker,
v.data(), v.size() / 2));
futures.emplace_back(std::async(std::launch::async, accumulate_block_worker,
v.data()+v.size()/2, v.size() / 2));
return futures;
}
future 클래스가 제공하는 wait()함수는 작업의 실행이 완료되어 함수가 데이터를 반환할 수 있는 상태가 되었을 때까지 기다리는 기능을 수행한다. 반면에 get()함수는 스레드로 실행되는 함수가 반환하는 데이터를 받는 역할을 수행한다. 따라서 만약 함수의 실행이 종료되지 않았다면 종료되어 반환받을 수 있을 때 까지 기다리게 된다. wait()외 wait_for(), wait_until()함수도 제공한다.
●promise 클래스 템플렛
작업 공간을 스레드별로 만든다. 하나의 promise객체를 통해 future객체가 생성 되었다면 공유할 작업 공간이 하나 생성 되었다는 것을 의미한다. async()함수가 아닌 promise객체를 사용한다면 개별 작업 공간을 통해 하나 이상의 스레드로 부터 동사에 작업한 결과를 수집할 수 있다는 이야기가 된다. 만들어 보자. 이 또한 코드 참조는 시작하자! C++17 프로그래밍 도서를 통해 코드를 작성하였다. 이번에는 주석을 잘 살펴주었으면 한다.
#include <iostream>
#include <future>
#include <thread>
#include <numeric>
#include <vector>
#include <functional>
void accumulate(std::vector<int>::iterator, std::vector<int>::iterator,std::promise<int>&&);
std::future<int> launch_promise(std::vector<int>::iterator, std::vector<int>::iterator);
int main(void)
{
int total = 0;
std::vector<int> numbers = { 1,2,3,4,5,6,7,8,9,10 };
//promise 객체가 작업할 환경을 만들고 futrue객체를 반환받는다.
std::future<int> s[2];
//6. promise객체로 부터 얻은 future객체를 통해 작업 결과를 얻는다.
s[0] = launch_promise(numbers.begin(), numbers.begin() + 6);
s[1] = launch_promise(numbers.begin() + 6, numbers.end());
for (int i = 0; i < 2; ++i) {
int w = s[i].get();
std::cout << "promise-" << i << " : " << w << std::endl;
total += w;
}
std::cout << "합계 : " << total << std::endl;
}
//3. promise객체를 인수로 사용하는 함수를 만든다.
void accumulate(std::vector<int>::iterator first, std::vector<int>::iterator last,
std::promise<int>&& accumulate_promise)
{
int sum = std::accumulate(first, last, 0);
//set_value() 함수는 future객체에 작업 결과를 전달하기 위해 사용한다.
//promise객체를 함수에 전달하는 인수는 반드시 rvalue참조르 사용해야 한다.
//함수 내부에서 사용하는 객체는 복사된 객체가 아닌 이동객체가 되어야 한다.
//7. 스레드로 실행되는 함수의 작업 결과를 set_value함수를 사용하여
// promise객체를 통해 future객체에 제공한다.
accumulate_promise.set_value(sum);
}
std::future<int> launch_promise(std::vector<int>::iterator first, std::vector<int>::iterator last)
{
// 1. promise객체를 생성한다.
std::promise<int> accumulate_promise;
// 2. promise객체가 제공하는 get_future함수를 사용하여 futrue객체를 얻는다.
std::future<int> result = accumulate_promise.get_future();
// 4. 스레드를 생성하고 실행시킨다.
std::thread work_thread(accumulate, first, last, std::move(accumulate_promise));
work_thread.detach();
return result;
}
최근 업데이트: 2021_03_14
부족한 부분이 있다면, 차후에 업데이트 하겠습니다.
'👨🏻💻 programming > ◽ c, c++' 카테고리의 다른 글
[C++] 제네릭 알고리즘 모음 (0) | 2022.05.16 |
---|---|
[C++] std::string_view 클래스 (0) | 2022.02.09 |
[C++] 연산자 오버로딩 프로토타입 & 한계 (0) | 2022.02.08 |
(전문가를 위한 C++/ 개정 4판) - I/O 입출력 완전 분석 (0) | 2022.01.31 |
(c++) 조건 변수(Conditional Variable) (0) | 2021.03.13 |
안 하는 것 보다 낫겠지
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!