Saturday, January 11, 2014

Chapter:13 Task Based Concurrency

The C++11 standard has also introduced the concept of task based concurrency. The main intention behind this concept is to provide the alternative for launching different task concurrently without the use of 'std::thread'. Task based concurrency also allows us to write lock free programming wherever possible.


Creating a thread to do the simple work has substantial overhead. In addition to that, user also needs to take care about the certain things like to wait for the completion of child thread in the parent thread context. This is pure blocking call and until child thread finishes its work, parent would not do anything useful except waiting.


If we think carefully, does it looks good to just wait in parent thread context?. I mean normally our intention behind the creating the thread is to execute tasks concurrently. However parent thread normally does not uses the result of its child thread immediately. Parent thread can do something useful in its context instead of just waiting. The moment  parent thread needs the result, it can fetch it and use it.


With 'std::thread' interfaces, we can start executing a thread function. Once thread function finishes, thread would eventually finish. However with this model, it is not possible to get the return value from the thread  function to its caller(parent). I mean there are ways which we can apply  to achieve this, like pass the return output variable in the thread function itself. However its not kind of very natural. When we write the function/task we pass some argument into it and once function finishes it return some value to its caller. By this way caller code can easily utilize the result of the task done.


This sort of mechanism has been provided in the task based concurrency and the main interface for this is "std::async". Its lightweight and should be  used to perform the basic task. It also provide the higher level of abstraction to the user. I mean user does not need to bother about whether system would create the thread or it would execute concurrently to perform the task provided by user.



The current program explains the basics usage of 'std::async'. User needs to pass the function name and the argument in the 'std::async'. This is same as we do it while creating the 'std::thread'. 'std::async' would return the handle to its caller which would be used by its caller to get the result whenever he wants. Here whenever is very important as parent can do its own useful work until he want to get the result of this particular task. If the task has not finished by the time parent is trying to get the result, it would result into the blocking call as it happens with the 'std::thread'. However if task has been executed successfully when parent is trying to get the result, there would not be any waiting/blocking call in the parent thread. This is key concept in this mechanism. We can think this handle as the communication medium between the caller and its callee.


Just to simulate this behavior, I have written the task/function which open the file and read the first line and return to its caller. However if file is not  available in the working directory, then the task would go to sleep for 2  seconds and again retry to open the file.


Here I have launched two tasks to open two different file and read their first  lines. 'std::async' returns the handle to its caller. After this, I have displayed the message to console in the parent context before fetching the result from both handles. During this time, parent can do its own useful work.


Parent thread would fetch the result from the handle by using the 'get()' interface. As I have mentioned that if the task has not finished, here parent would have to wait for the result. To verify this we can remove one input file before you run this program. Once program waits on the line where parent thread has called to get the result, we can put the back the input file to the program directory. The moment we do this, program would print the concatenation of both strings onto console. With this program would complete sucessfully. Hope this make sense to the reader.



//Code Begins

#include<iostream>
#include<fstream>
#include<string>
#include<thread>
#include<future>
#include<chrono>
using namespace std::chrono;

template<typename T>
void display(const T& val) {
    std::cout<<val<<std::endl;
}

std::string readline_from_file(std::string fname) {
    std::string output;
    start:
    std::fstream fs{fname, std::ios_base::in};
    if(!fs.is_open()) {
        std::this_thread::sleep_for(seconds(2))    ;
        goto start;
    }
    else {
        std::getline(fs,output);
        return output;
    }
}

void learn_task_based_concurrency(void)
{
    std::string file_x="input_x";
    std::string file_y="input_y";
    auto fd_x=std::async(readline_from_file,file_x);
    auto fd_y=std::async(readline_from_file,file_y);
    display("Waiting For the Result...");

    std::string out_x=fd_x.get();
    std::string out_y=fd_y.get();

    std::string output=out_x +" "+ out_y;
    display(output);
}


int main(int argc, const char* argv[])
{
    learn_task_based_concurrency();
    return 0;
}

//Code Ends




When I run the above program, I get the following output.


//Console Output Begins

$ ./test
Waiting For the Result....
input_x input_y

//Console Output Ends




In this program, I would explain the usage of 'std::packaged task'.  Practially, we can think about 'std::async' as the specialization  of this interface. We can think of 'std::packaged task' as the  complete object which contains all the information which caller  would need in future to get the result from the task. This way  it encapsulate all important information at one place in a object.


In addition to that it also breaks the steps of initialization and  actual execution in different steps which kind of give more  flexibilty to user. I think this is more like object oriented  apporach against the 'std::async' interface which looks more  of functional type.


In this program, I have created two tasks in parent thread  context. First we need to create the object of 'std::packaged task'. Watch out for the syntax in the left hand side of expression. Its template on the type of the function, so we need to pass this  information. Once objet has been created, we can pass the argument which would invoke the task execution. Rest of the interface are  kind of same as the 'std::async'.


In my view, we can also think 'std::packaged task' as better version  of raw function pointer. It provides better abstraction and it  also encapsulate all the relevant information(like argument, result) into one place.



//Code Begins

#include<iostream>
#include<string>
#include<thread>
#include<future>
#include<exception>

template<typename T>
void display(const T& val) {
    std::cout<<val<<std::endl;
}

template<typename Tout, typename Targs>
Tout task_sum(Targs i1, Targs i2) {
    Tout tmp{};
    tmp = i1 + i2;
    return tmp;
}

template<typename Tout, typename Targs>
Tout task_sub(Targs i1, Targs i2) {
    Tout tmp{};
    tmp = i1 - i2;
    return tmp;
}


void learn_task_based_concurrency(void) {
    std::packaged_task<int (int,int)> pt_x {task_sum<int,int>};
    std::packaged_task<int (int,int)> pt_y {task_sub<int,int>};
    pt_x(20,10);
    pt_y(20,10);
    auto f_x = pt_x.get_future();
    auto f_y = pt_y.get_future();
    display(f_x.get());
    display(f_y.get());
}


int main(int argc, const char* argv[]) {
    learn_task_based_concurrency();
    return 0;
}

//Code Ends



When I run the above program, I get the following output.




//Console Output Begins

$ ./test
30
10

//Console Output Ends




No comments:

Post a Comment