/MAN

Man is Thread Pool in C++17

Primary LanguageC++

Introduction

MAN is a ThreadPool wrote in C++17. The name is chosen because, at least in France, it is said that men are not able to do several things at the same times.

Different classes

Runnable

Introduction

The class Runnable is defined like that :

class Runnable<Args...>;

The Args... are the types of the arguments to give to the Runnable::operator(); or its launch method.

Features

  • Can handle Functors.
  • Can handle Lambdas.
  • Can handle function pointers.
  • Can retrieve the result if there is any.
  • Can retrieve the elapsed time since the beginning of the task.
  • Can retrieve the progression if there is one available
  • Can retrieve the remaining time if there is a progression.
  • Can retrieve the issues if there are some issues

RunnableQueue

Introduction

The class RunnableQueue is defined as follow :

class RunnableQueue<type_list<ContextsAndArgs...>, type_list<OnlyArgs...>>;

The ContextsAndArgs... represents all the arguments that the Runnable will take.

  • Contexts mean arguments that live on the thread on which the Runnable will be running.
  • Args mean the argument that you will directly pass to the Runnable when you call launch.

ThreadPool

Introduction

The classes for manage a thread pool are defined as follow :

class ThreadPoolWithContextsAndArgs<type_list<Contexts...>, type_list<Args...>>;
using ThreadPool = ThreadPoolWithContextsAndArgs<type_list<>, type_list<>>;

template<typename ...Args>
using ThreadPoolWithArgs = ThreadPoolWithContextsAndArgs<type_list<>, type_list<Args...>>;

template<typename ...Contexts>
using ThreadPoolWithContext = ThreadPoolWithContextsAndArgs<type_list<Contexts...>, type_list<>>;

How to use it ?

The first thing to do is to create a function to run.

struct Test {
    int operator()(int *a, int b) noexcept {
        auto r = *a + b;
        delete a;
        return r;
    }
};

Here, the function takes two arguments : int*, int. Let's assume that the first argument is a context variable, and the second is a direct argument.

The second thing to do is to create a thread pool. We must give to the thread pool the context types, which is an int*, and an argument type, which is an int here. We also need to give a function to initialize each context variables. After, we call the function addRunnable and give it the function and the arguments. After we wait for all tasks within the threadPool are finished (we could also do it for the task only) and we check the result.

man::ThreadPoolWithContextsAndArgs<man::type_list<int*>, man::type_list<int>>
        poolContext{1,
                    []{return new int{42};}};
auto runnable = poolContext.addRunnable(Test{}, 42);
poolContext.wait();
assert(runnable->getResult<int>() == 42 + 42);

Futures improvements

  • Runnable : No dynamic allocation, use aligned_storage instead.
  • Runnable : Allow to use only one type to avoid virtual calls.
  • Creates a promise / future types.
  • Allows continuation chains : runnable->then(foo);
  • Make it compatible with C++20 stackless coroutine / or self made coroutines?