I have addressed the critique for this post and resubmitted it for iterative review; C++ multithread pool class.
Class for creating thread pools with the latest C++ standard. Currently C++17 and C++2a.
- It currently only accepts and executes parameterless routines.
- Multiple functions may be enqueued via variadic template or stl vector.
- The number of threads created in the pool is relative to the hardware concurrency by means of a user multiplier. If the hardware concurrency cannot be determined it will default to four and the number of threads created will be two, four, or eight depending on the multiplier used.
Please review code correctness, best practices, design and implementation.
Please assume the namespace Mercer is to be used as a cross-platform library.
This code was also available on GitHub, but now contains current iteration.
mercer.h
//File mercer.h
//Author Michael Mercer
//Copyleft CC BY-SA
//Description Header for universal declarations used in namespace Mercer
#ifndef MERCER_H_0000
#define MERCER_H_0000
/*#####################----- Mercer -----###################*/
/* universal declarations */
namespace Mercer
{
enum struct execution: bool {failure, success};
}
/* */
/*#####################----- Mercer -----###################*/
#endif //MERCER_H_0000
multithread.h
//File multithread.h
//Author Michael Mercer
//Copyleft CC BY-SA
//Description Header for multithread class
#ifndef MULTITHREAD_H_0000
#define MULTITHREAD_H_0000
/*#####################----- multithread -----###################*/
/* class for multithread interface */
/* GCC Bug 84330 - [6/7 Regression] [concepts] ICE with broken constraint
#ifndef __cpp_concepts
static_assert(false, "Concepts TS NOT found");
#endif
#include <type_traits>
template<typename dataType>
concept bool Function()
{
return std::is_function<dataType>::value;
}
*/
#include <deque>
#include <queue>
#include <mutex>
#include <vector>
#include <memory>
#include <thread>
#include <functional>
#include <condition_variable>
#include "mercer.h"
namespace Mercer
{
//Multithread class
//if !joinable no new routines may be enqueued
class multithread
{
class implementation;
std::unique_ptr<implementation> data;
public:
enum struct concurrency: int {half, full, twofold};
multithread(concurrency quantity);
multithread(); //concurrency::full
~multithread();
execution enqueue(const std::vector<std::function<void()>>&&);
//consumes std::vector iff execution::success
execution enqueue(const std::function<void()>&&);
template<typename ... dataType>
execution enqueue(const std::function<void()>&& proximate ,
const std::function<void()>&& penproximate,
dataType ... parameters )
{
if(execution::success==
enqueue(std::forward<const std::function<void()>>(proximate ) ))
enqueue(std::forward<const std::function<void()>>(penproximate) ,
std::forward<dataType >(parameters )...);
else
return execution::failure;
return execution::success;
}
execution join();
execution detach();
bool thrown() const noexcept;
std::exception_ptr getNextException() const;
//If thrown()==true, will never throw
//If get final exception, thrown() will reset to false
};
}//namespace Mercer
/* */
/*#####################----- multithread -----###################*/
#endif //MULTITHREAD_H_0000
multithread.cpp
//File multithread.cpp
//Author Michael Mercer
//Copyleft CC BY-SA
//Description Source for multithread class
/*#####################----- multithread -----###################*/
/* class for multithread interface */
#include "multithread.h"
using Mercer::multithread;
using Mercer::execution;
using function = std::function<void()>;
struct multithread::implementation
{
enum struct close: bool {detach, join};
std::queue<std::exception_ptr> exceptions;
bool open ;
std::deque <function> line ;
std::mutex door ;
std::condition_variable guard;
std::vector<std::thread> pool ;
implementation(concurrency quantity) :
open(true),
line(),
door(),
guard(),
pool(std::invoke( [&]
{
std::vector<std::thread> temp;
unsigned threads = std::thread::hardware_concurrency();
if(threads==0)
threads=4;
switch(quantity)
{
case concurrency::half : threads /= 2; break;
case concurrency::full : break;
case concurrency::twofold: threads *= 2; break;
}
temp.reserve(threads);
for(auto i=threads; i>0; i--)
temp.emplace_back( [&]
{
function next;
bool perpetual = true;
while(perpetual)
{
std::unique_lock lock(door);
guard.wait(lock, [&]
{
return !line.empty() || !open;
} );
if(!line.empty())
{
next = std::forward<function>(line.front());
line.pop_front();
if(!open && line.empty())
perpetual = false;
lock.unlock();
guard.notify_one();
try
{
next();
}
catch(...)
{
exceptions.emplace(
std::current_exception() );
}
}
else if(!open)
perpetual = false;
}
}
);
return temp;
}) )
{}
template<close closeType>
execution close()
{
auto result = execution::success;
if (open==true)
{
open = false;
guard.notify_all();
for (auto&& thread : pool)
if (thread.joinable())
switch(closeType)
{
case close::join : thread.join() ; break;
case close::detach: thread.detach(); break;
}
pool.clear();
pool.shrink_to_fit();
}
else
result = execution::failure;
return result;
}
};
multithread::multithread(concurrency quantity):
data(std::make_unique<implementation>(quantity))
{}
multithread::multithread():
data(std::make_unique<implementation>(concurrency::full))
{}
execution multithread::join()
{
return data->close<implementation::close::join>();
}
execution multithread::detach()
{
return data->close<implementation::close::detach>();
}
multithread::~multithread()
{
join();
}
execution multithread::enqueue(const function&& item)
{
auto result = execution::success;
if (data->open==true)
{
std::scoped_lock(data->door);
data->line.emplace_back(std::forward<const function>(item));
data->guard.notify_all();
}
else
result = execution::failure;
return result;
}
execution multithread::enqueue(const std::vector<function>&& adjunct)
{
auto result = execution::success;
if (data->open==true)
{
std::scoped_lock(data->door);
data->line.insert(data->line.end(),
make_move_iterator(adjunct.begin()) ,
make_move_iterator(adjunct.end() ));
data->guard.notify_all();
}
else
result = execution::failure;
return result;
}
bool multithread::thrown() const noexcept
{
return data->exceptions.empty() ? false : true;
}
std::exception_ptr multithread::getNextException() const
{
if(thrown())
{
auto temp = std::forward<std::exception_ptr>(data->exceptions.front());
data->exceptions.pop();
return temp;
}
else
throw std::out_of_range("Thrown() is false, no exception to get");
}
/* */
/*#####################----- multithread -----###################*/
//Description Header for universal declarations used in namespace Mercer
. If you are going to have comments (a code smell for now writing readable code). Then they should be meaningful. Maintaining comments is just as important as maintaining the code. Garbage/Useless comments are not maintained and they will fall out of sync with the code. When code and comments are out of sync hell is unleashed. Is the code correct and comments wrong or vice versa. How can I tell. The answer: Only write comments that provide real information. \$\endgroup\$GCC Bug 84330 - [6/7 Regression] [concepts]
. Would have been nice with a link so we can go look at the bug report. \$\endgroup\$NAMED
functions. \$\endgroup\$