Saturday 12 October 2013

std::async, std::package_task and std::promise

    c++11 offer us a thread library with ease of use api(although it still lack decent thread pool and high level algorithms like intel TBB or QtConcurrent provided), but I always confused with std::async, std::package_task and std::promise, so I write down this post to help me memorize the different between them.

    std::async

   we could spawn a thread by std::thread, the api of std::thread is simple and clean, but what if we want to take back the result after process?std::thread do not give us a direction way to do that, this is the time when std::async comes in.

    std::async is a high level api for us to start an asynchronous task when we don't need to take the result back right away.



#include <algorithm>
#include <future>
#include <iostream>
#include <iterator>
#include <vector>


inline int accumulate(std::vector<int> const &data)
{
  return std::accumulate(std::begin(data), std::end(data), 0);
}

struct Accmulate
{
    int operator()(std::vector<int> const &data)
    {
        return std::accumulate(std::begin(data), std::end(data), 0);
    }
};

int main()
{
  std::vector<int> data(100);
  std::iota(std::begin(data), std::end(data));

  //called by function
  std::future<int> result = std::async(accumulate, std:cref(data));  
  std::cout<<result.get()<<std::endl; //#1

  //called by functor
  Accmulate acc;
  std::future<int> result2 = std::async(&Accmulate::operator(), 
                                              &acc, std::cref(data));
  std::cout<<result2.get()<<std::endl; //#2
}


At #1 and #2 the program will wait until the function and the functor finish the tasks.There are three additional flag for std::async, they are std::launch::async and std::launch::deferred.

  • case a : if the std::launch::async is set, the std::async will execute the function or functor in a new thread as if spawned by std::thread.
  • case b : if the std::launch::deferred is set, the function call will be deferred until wait() or get() is called by the future.
  • By default, the implementation will choose which case should be picked, either case a or case b. 

std::package_task

  What if we need more sophisticated control about std::async, like saving a lot of "tasks", defer the execution of those asynchronous or concurrent tasks?This is what std::package_task good for.


#include <future>
#include <iostream>
#include <vector>

inline int max(int a, int b)
{
    return std::max(a, b);
}

int main()
{    
    std::vector<std::packaged_task<int(int, int)>> tasks;
    std::vector<std::future<int>> futures;
    for(size_t i = 0; i != 4; ++i){
        std::packaged_task<int(int, int)> task(&max);
        futures.emplace_back(task.get_future());
        tasks.push_back(std::move(task));
    }
    
    //run the tasks asynchronous
    std::thread t1(std::move(tasks[0]), 3, 4);
    std::thread t2(std::move(tasks[1]), 4, 5);
    //run the tasks concurrent
    tasks[2](5, 4);
    tasks[3](3, 2);

    for(auto &data : futures){
        std::cout<<data.get()<<std::endl;
}


   The codes seem no harms at first, but what if exception thrown?Well, boost::scoped_thread(this should be part of the standard) could help us guard the thread and make sure it will call "join()" when leaving the scope.

std::promise

    The promise is a building block to communicate with future.We could build std::async by std::package_task;build std::package_task by std::promise.


// promise example
#include <iostream>
#include <functional>
#include <thread>
#include <future>

void test(std::future<int>& input) {
  std::cout << fut.get() << std::endl;
}

int main ()
{
  std::promise<int> pm;

  std::future<int> result = pm.get_future();

  std::thread th1 (test, std::ref(result));

  pm.set_value (10); #3
                            
  th1.join();
  return 0;
}

    we set the value of promise and #3(fulfilled the promise), if we do not set the value before of promise and let the promise exit in this case, the program will throw exception(we can set the exception promise would throw by set_exception()).We cannot

  • case 1 : call set_value() of a same promise more than one time
  • case 2 : call get_value() of a same promise more than one time
  • case 3 : If the promise die without consequence(call set_value()) and the users call the get() of corresponding future, exception will throw.
  • case 4 : promise can die without consequence, but the users must not call the get() of corresponding future
case 1 :
void test()
{
  std::promise<int> pm;
  pm.set_value(10);
  pm.set_value(10); //throw exception
}


case 2 :
void test()
{
  std::promise<int> pm;
  std::future<int> ft1 = pm.get_future();
  std::future<int> ft2 = pm.get_future(); //throw exception
}


case 3 :
void test()
{  
  std::future<int> ft;
  {
    std::promise<int> pm;
    ft = pm.get_future();
  }
  ft.get(); //throw exception
}


case 4 :
void test()
{
  std::future<int> ft;
  {
    std::promise<int> pm;
    ft = pm.get_future();
  }
  //ft.get(); if you don't call it, the exception wouldn't thrown
}


No comments:

Post a Comment