Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Managing threads while practicing modern c++17's best practices

Tags:

Originally I had thought about designing a ThreadManager class to store threads along with the data type objects and function type objects that they would work with. The class was to be responsible for the managing of memory, access, transferring, releasing, locking, unlocking, joining, and other typical common functionalities of the associated types within the standard multithreading library. It was originally intended to associate the containing thread and its id with a specific set of resources that a particular thread has access to.

After reading through the docs on cppreference about mutex, shared_mutex, lock_guard, shared_lock, std::function<...>, etc. and now knowing that mutexes and lock_guards are non copyable and the fact that if I template the class to store arbitrary function objects, function pointers, lambdas or std::function<>s as a std::function<> within this class's container that the class instantiation of this intended singleton would only be able to store a specific function signature limiting it to not be able to instantiate any other declaration signature.

Concerning these behaviors and properties of the multithreading library within the standard library for mutexes, shared_mutexes lock_guards, threads, promises, futures etc... it came to mind that I'm overthinking the overall design of this class.

You can refer to my initial design attempt via this previously asked question of mine. Storing arbitrary function objects into a class member container without knowing their declaration signature, This should give you an idea of what I was attempting to do.


Understanding a little more about their behaviors, properties and responsibilities I would like to know if the following would be appropriate for the intended design process.

Instead of storing any mutexes, lock_guards, threads, data type objects or function objects; would it make more sense to just store the generated ids of the threads, and have my manager class act more like a monitoring, recording, and reporting type class instead?

My new intentions would be that the container would store the thread's ID in an associated map as its key along with an associated common struct. The struct would contain a property list of all the responsibilities and actions of the combined resources. This may then allow support for some of the following features: a priority queue, a task scheduler, a dispatcher of commands to send and fetch resources knowing if the thread is available or not, where these types of actions will not be done by this class directly but through generic function templates.

For example:

struct ThreadProperties {
    // thread specific
    id,
    slept for,
    is locked,
    is waiting,
    is joined,
    has mutex, if so hold id to mutex - lockguard
    is shared,
    is active,
    has promise or future...
    who has ownership of,
    marked for release,
    marked for transfer

    // other mutex and lock_guard properties

    // function object address stored as `size_t` to represent an id
    // data object address stored as `size_t` to represent an id

    // etc.

};

class ThreadManager {
private:
    std::map<unsigned, ThreadProperites> threadTable;
public:
    default constructor

    storeIds into relevant containers

    store properties into relevant containers

    associate above containers into a map or lookup table

    find or look for specific ID and if found 
    check to see its current status and report it
    also check to see if it's in a priority queue 
    or task scheduler and determine if it is ready
    to do something, or change its internal state.

    other common methods of functionality associated with threads.
};

// function templates to act on threads according to the reporting of the manager above.

With my intention to implement this kind of design while trying to maintain the best practices of modern c++ targeting c++17; would this kind of design be appropriate for proper class design to be generic, modular, portable and efficient for use?

like image 986
Francis Cugler Avatar asked Mar 22 '18 23:03

Francis Cugler


1 Answers

Threads and other std primitives are like raw pointers. You should build up a concurrency model that doesn't expose anything that low level. The std threading primitives provide you with enough tools to do so.

Learn about some of the new stuff headed down the pipe -- executors, streams, coroutines, rangesv3, monadic futures, etc. Model your library around that.

Trying to make well behaved code based around raw use of mutexes, threads going to sleep and waking up, blocking, atomics and shared data is a trap.

As an example:

struct thread_pool;
template<class T>
struct my_future:std::future<T> {
  template<class F>
  auto then( F&& f )&&
  -> std::future< std::result_of_t<F(T&&)> >;
  thread_pool* executor = 0;
};
template<>
struct my_future<void>:std::future<void> {
  template<class F>
  auto then( F&& f )&&
  -> std::future< std::result_of_t<F()> >;
  thread_pool* executor = 0;
};
struct thread_pool {
  template<class F=do_nothing>
  my_future<std::result_of_t<F()>> do_task(F&& f={});
};

here we talk about piping data from task to task to task and ending in an augmented future<T>. Augment it with the ability to split (via shared_future) and merge (future<X> joined with future<Y> to produce future<X, Y>).

Maybe go a step further and build a stream based system:

template<class In>
using sink = std::function<void(In)>;
template<class Out>
using source = std::function<sink<Out>>;
template<class In, class Out>
using pipe = std::function< source<In>, sink<Out> >;

and then support turning a source into an async source.

And instead of building up some huge castle of abstraction and hoping it is complete, read about these things, and when you run into a problem that one of these things solve implement just enough to solve your problem. You aren't writing the be-all end-all thread system from scratch the first time you try, so don't try. Write something useful, and then write a better one next time.

like image 195
Yakk - Adam Nevraumont Avatar answered Sep 20 '22 13:09

Yakk - Adam Nevraumont