packagedTask

Asynchronous Callable Wrappers

std::packaged_task enables you to write a simple wrapper for a callable, which you can invoke later.

std::packaged_task

To deal with std::packaged_task, you have to perform four steps usually:

  1. Wrap the task
  2. Create the future
  3. Perform the calculation
  4. Pick up the result

These steps are easy to get with an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// packagedTask.cpp

#include <utility>
#include <future>
#include <iostream>
#include <thread>
#include <deque>

class SumUp{
  public:
    int operator()(int beg, int end){
      long long int sum{0};
      for (int i= beg; i < end; ++i ) sum += i;
      return sum;
    }
};

int main(){

  std::cout << std::endl;

  SumUp sumUp1;
  SumUp sumUp2;
  SumUp sumUp3;
  SumUp sumUp4;

  // define the tasks
  std::packaged_task<int(int,int)> sumTask1(sumUp1);
  std::packaged_task<int(int,int)> sumTask2(sumUp2);
  std::packaged_task<int(int,int)> sumTask3(sumUp3);
  std::packaged_task<int(int,int)> sumTask4(sumUp4);

  // get the futures
  std::future<int> sumResult1= sumTask1.get_future();
  std::future<int> sumResult2= sumTask2.get_future();
  std::future<int> sumResult3= sumTask3.get_future();
  std::future<int> sumResult4= sumTask4.get_future();

  // push the tasks on the container
  std::deque< std::packaged_task<int(int,int)> > allTasks;
  allTasks.push_back(std::move(sumTask1));
  allTasks.push_back(std::move(sumTask2));
  allTasks.push_back(std::move(sumTask3));
  allTasks.push_back(std::move(sumTask4));
  
  int begin{1};
  int increment{2500};
  int end= begin + increment;

  // execute each task in a separate thread
  while ( not allTasks.empty() ){
    std::packaged_task<int(int,int)> myTask= std::move(allTasks.front());
    allTasks.pop_front();
    std::thread sumThread(std::move(myTask),begin,end);
    begin= end;
    end += increment;
    sumThread.detach();
  }

  // get the results
  auto sum= sumResult1.get() + sumResult2.get() + sumResult3.get() + sumResult4.get();

  std::cout << "sum of 0 .. 10000 = " << sum << std::endl;

  std::cout << std::endl;

}

 

The job of the program is not so heavy. Calculate the sum from 0 to 10000 with the help of four threads and sum up the results with the associated futures. Of course, you can use the Gaußschen Summenformel. (Strange, there is no English page describing this famous algorithm. But math is international.).

I pack the work packages in std::packaged_task (lines 28 – 31) objects in the first step. Work packages are instances of the class SumUp (lines 9 – 16). The call operator does the current work (lines 11-15). The call operator sums up all numbers from beg to end and returns the sum. std::packaged_task in lines 28 – 31 can deal with callables that need two ints and return an int.

Now, I have to create in the second step the future objects with the help of std::packaged_task objects. Precisely that is done in lines 34 to 37. The packaged_task is the promise in the communication channel. In these lines, I define the type of the future: std::future<int> sumResult1= sumTask1.get_future(), but of course, I can do it also implicitly: auto sumResult1= sumTask1.get_future().

In the third step, the work takes place. The packaged_task is moved onto the std::deque (lines 40 – 44). Each packaged_task (lines 51 – 58) is executed in the while loop. To do that, I move the head of the std::deque in a std::packaged_task (line 52), move the packaged_task in a new thread (line 54) and let it run in the background (line 56). std::packaged_task object is not copyable. That’s why I used move semantic in lines 52 and 54. This restriction holds for all promises but also futures and threads. There is one exception to this rule: std:.shared_future.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    In the fourth and last step, the four futures ask with the get call for the results and sum them up (line 61).

    Honestly, std::packaged_task was not made for a simple use case like std::async. But the result is simple.

    packagedTask

    Optimization potential

    C++11 has a function std::thread_hardware_concurrency. It provides a hint about the number of cores on your system. If the C++ runtime has no glue, it conforms to the standard to return 0. So you should verify that value in your program (line 31). With the current GCC, Clang, or Microsoft compiler, I get the right answer 4. I use this number of cores In the variation of the program packagedTask.cpp, to adjust the software to my hardware. So, my hardware is fully utilized.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // packagedTaskHardwareConcurrency.cpp
    
    #include <algorithm>
    #include <future>
    #include <iostream>
    #include <thread>
    #include <deque>
    #include <vector>
    
    class SumUp{
    public:
      SumUp(int b, int e): beg(b),end(e){}
      int operator()(){
        long long int sum{0};
        for (int i= beg; i < end; ++i ) sum += i;
        return sum;
      }
    private:
        int beg;
        int end;
    };
    
    static const unsigned int hwGuess= 4;
    static const unsigned int numbers= 10001;
    
    int main(){
    
      std::cout << std::endl;
    
      unsigned int hw= std::thread::hardware_concurrency();
      unsigned int hwConcurr= (hw != 0)? hw : hwGuess;
    
      // define the functors
      std::vector<SumUp> sumUp;
      for ( unsigned int i= 0; i < hwConcurr; ++i){
        int begin= (i*numbers)/hwConcurr;
        int end= (i+1)*numbers/hwConcurr;
        sumUp.push_back(SumUp(begin ,end));
      }
    
      // define the tasks
      std::deque<std::packaged_task<int()>> sumTask;
      for ( unsigned int i= 0; i < hwConcurr; ++i){
        std::packaged_task<int()> SumTask(sumUp[i]);
        sumTask.push_back(std::move(SumTask));
      }
    
      // get the futures
      std::vector< std::future<int>> sumResult;
      for ( unsigned int i= 0; i < hwConcurr; ++i){
        sumResult.push_back(sumTask[i].get_future());
      }
    
      // execute each task in a separate thread
      while ( not sumTask.empty() ){
        std::packaged_task<int()> myTask= std::move(sumTask.front());
        sumTask.pop_front();
        std::thread sumThread(std::move(myTask));
        sumThread.detach();
      }
    
      // get the results
      int sum= 0;
      for ( unsigned int i= 0; i < hwConcurr; ++i){
        sum += sumResult[i].get();
      }
    
      std::cout << "sum of 0 .. 100000 = " << sum << std::endl;
    
      std::cout << std::endl;
    
    }
    

     

    What’s next?

    The next post will be about futures and promises. (Proofreader Alexey Elymanov)

     

     

     

     

     

     

     

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *