ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • packaged_task를 이용한 스레드 풀 구현 1
    Modern C++/Task 기반 비동기 프로그래밍 2020. 8. 28. 05:54

    이전 장에서 std::async 함수를 통해서 비동기 태스크를 생성하는 방법과 내부에서 std::promise<T> 와 std::future<T> 객체를 통해서 스레드에서 어떻게 future 객체에 결과를 전달했는 지를 살펴 보았다. C++ 은 std::async 이외에도 packaged_task를 지원해 해당 태스크를 다른 스레드에 전달해 이를 수행할 수 있다. 

    #include <iostream>
    #include <thread>
    #include <future>
    
    int add(int a, int b) {
        std::cout << "[thread id:"
                     << std::this_thread::get_id() << "]  a + b = "
                     << a + b << std::endl;
        return a + b;
    }
    
    int main() {
        //두 int 인자를 받아 int를 반환하는 패키지 태스크 생성
        std::packaged_task<int(int, int)> task(add);
        //태스크의 future 객체 지정 태스크는 내부적으로 promise 객체에 반환 값을 설정
        std::future<int> ret = task.get_future();
        //태스크 객체를 실행할 스레드 지정
        std::thread(std::move(task), 1, 2).join();
        std::cout << "[thread id: " << std::this_thread::get_id() << " ] ret = "  << ret.get() << std::endl;
    
        return 0;
    }
    
    

     

    동일하게 std::bind를 이용해 함수와 인수를 바인한 packaged_task(int())를 생성해 스레드에 전달할 수 있다. 

    #include <iostream>
    #include <thread>
    #include <future>
    
    int add(int a, int b) {
        std::cout << "[thread id:"
                  << std::this_thread::get_id() << "]  a + b = "
                  << a + b << std::endl;
        return a + b;
    }
    
    int main() {
        //함수와 인자들을 바인드한 패키지 객체를 스레드에 전달하는 방법  
        auto task = std::packaged_task<int()>(std::bind(add, 10, 20));
        auto ret = task.get_future();
        std::thread(std::move(task)).join();
        std::cout << "[thread id: " << std::this_thread::get_id() << " ] ret = "  << ret.get() << std::endl;
    
        return 0;
    }
    
    
    

     

    위 처럼 특정 태스크를 수행할 수 있는 스레드를 지정할 수 있다면   패키지 태스크를 저장할 큐를 생성하고 해당 큐를 스레드들이 루프를 돌면서 큐에 태스크를 가져와 작업을 수행하는 방식의 스레드 풀을 만들 거나, network, db, file io 관련 스레드를 분리하고 각각에 대한 스레드와 태스크 큐를 분리해 관리한다면 과도한 스레드로 인한 문제를 해결할 수 있다. 

    1. std::async() 와 같은 파라미터와 타입을 가지는 async() 함수 구현해 보기 

    std::async() 함수는 아래와 같이 정의 되어 있고 Function 과 가변 인자 Arg 템플릿을 사용한다. 이와 유사하게 함수와 인수목록을 가변적으로 받을 수 있는 asynInThreadpool 함수의 프로토 타입을 구현해 본다.  

    template< class Function, class... Args >
    std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
    async( std::launch policy, Function&& f, Args&&... args );
    #include <iostream>
    #include <thread>
    
    template <typename Func, typename... Args>
    std::future<std::result_of_t<std::decay_t<Func>(std::decay_t<Args>...)>> asyncTask(Func&& func, Args... args) {
        using Result = std::result_of_t<std::decay_t<Func>(std::decay_t<Args>...)>;
        //std::bind를 통해 packaged_task 생성
        std::packaged_task<Result()> task(std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
        //반환할 future 객체 설정
        auto fut = task.get_future();
        std::thread([&task = task](){
            task();
        }).join();
        return fut;
    }
    
    
    int main() {
    
        auto future = asyncTask([](int a, int b) { return a + b; }, 10, 20);
        std::cout << future.get() << std::endl;
        return 0;
    }

    위를 통해 원하는 결과가 정상적으로 동작됨을 확인할 수 있다. 스레드에 안전한 태스크 큐와 실제 lambda를 큐로 push하고 pop하는 루틴은 다음 시간에 구현하도록 한다. 

     

    댓글

Designed by Tistory.