2016-05-02 14:49:52 +00:00
|
|
|
//
|
|
|
|
// main.cpp
|
|
|
|
// thread_pool v0.2a
|
|
|
|
//
|
|
|
|
// Created by Kristof Toth on 26/06/15.
|
|
|
|
//
|
2016-11-12 09:43:18 +00:00
|
|
|
// Copyright © 2015 Kristof Toth <mrtoth@strongds.hu>
|
2016-11-18 22:06:30 +00:00
|
|
|
// This program is free software. It comes without any warranty, to
|
|
|
|
// the extent permitted by applicable law. You can redistribute it
|
|
|
|
// and/or modify it under the terms of the Do What The Fuck You Want
|
|
|
|
// To Public License, Version 2, as published by Sam Hocevar. See
|
|
|
|
// http://www.wtfpl.net/ for more details.
|
2016-05-02 14:49:52 +00:00
|
|
|
//
|
|
|
|
// Generalized thread pool class.
|
|
|
|
// ( READ BEFORE USING IN PRODUCTION CODE )
|
|
|
|
// User's notes:
|
|
|
|
// - the recommended way to add a task to the queue is to use the
|
|
|
|
// add_task(F&& f, Args&&... args) format, where f is any kind of
|
|
|
|
// functor and (args...) are the parameters to the functor (any number/kind).
|
|
|
|
// - copying & moving not allowed (not sure if they'd make sense)
|
|
|
|
// - if a task is added to the pool via add_task(std::packaged_task<R()>& ),
|
|
|
|
// the client must grant, that the packaged_task object is NOT
|
|
|
|
// destructed before the task is finished. (otherwise it's undefined behaviour)
|
|
|
|
// this way of adding tasks is only recommended for micro-operations,
|
|
|
|
// where the overhead of add_task(F&& f, Args&&... args)'s use of operator new
|
|
|
|
// is too much (this is rarely the case).
|
|
|
|
//
|
|
|
|
|
|
|
|
/* necessary includes */
|
|
|
|
#include <iostream>
|
|
|
|
#include <thread>
|
|
|
|
#include <future>
|
|
|
|
#include <mutex>
|
|
|
|
#include <atomic>
|
|
|
|
#include <vector>
|
|
|
|
#include <deque>
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <functional>
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
/* for the tests */
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
|
|
#include <boost/type_index.hpp>
|
2016-05-02 15:38:08 +00:00
|
|
|
//#include <boost/asio/io_service.hpp>
|
|
|
|
//#include <boost/bind.hpp>
|
|
|
|
//#include <boost/thread/thread.hpp>
|
|
|
|
|
|
|
|
|
2016-05-02 14:49:52 +00:00
|
|
|
|
|
|
|
class thread_pool
|
|
|
|
{
|
|
|
|
/* under the hood */
|
|
|
|
std::mutex mu;
|
|
|
|
std::condition_variable cond;
|
|
|
|
std::vector<std::thread> workers;
|
|
|
|
std::deque<std::function<void()> > queue;
|
|
|
|
std::atomic<bool> fin;
|
|
|
|
|
|
|
|
/* the main loop of the threads */
|
|
|
|
void loop();
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
/* construction & destruction */
|
2016-11-12 09:55:22 +00:00
|
|
|
explicit thread_pool(size_t);
|
2016-05-02 14:49:52 +00:00
|
|
|
~thread_pool();
|
|
|
|
|
|
|
|
/* disallowed operations */
|
|
|
|
thread_pool(const thread_pool&) = delete;
|
|
|
|
thread_pool& operator=(const thread_pool&) = delete;
|
|
|
|
thread_pool(thread_pool&&) = delete;
|
|
|
|
thread_pool& operator=(thread_pool&&) = delete;
|
|
|
|
|
|
|
|
template <typename R> void add_task(std::packaged_task<R()>&& ) = delete;
|
|
|
|
template <typename R> void priority_task(std::packaged_task<R()>&& ) = delete;
|
|
|
|
|
|
|
|
/* adding tasks to the queue */
|
2016-11-22 16:35:57 +00:00
|
|
|
// TODO: use template-based policies to deal with priority tasks to avoid code-dupe.
|
2016-05-02 14:49:52 +00:00
|
|
|
template <typename F, typename... Args>
|
|
|
|
auto add_task(F&& f, Args&&... args) -> std::future<decltype(f(args...))>;
|
|
|
|
|
|
|
|
template <typename F, typename... Args>
|
|
|
|
auto priority_task(F&& f, Args&&... args) -> std::future<decltype(f(args...))>;
|
|
|
|
|
|
|
|
template <typename R> void add_task(std::packaged_task<R()>& );
|
|
|
|
template <typename R> void priority_task(std::packaged_task<R()>& );
|
|
|
|
// template <typename R> void add_task(std::function<R()> ); << think about this
|
|
|
|
void add_task(std::function<void()> );
|
|
|
|
|
|
|
|
/* other operations */
|
|
|
|
void add_thread(size_t);
|
|
|
|
|
|
|
|
/* means of getting information */
|
|
|
|
inline size_t get_thread_num() const { return workers.size(); }
|
|
|
|
inline size_t get_queue_size() const { return queue.size(); }
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2016-11-12 09:55:22 +00:00
|
|
|
thread_pool::thread_pool(size_t thcount):
|
2016-05-02 14:49:52 +00:00
|
|
|
fin(false)
|
|
|
|
{
|
2016-11-12 09:55:22 +00:00
|
|
|
for (size_t i = 0; i < thcount ; ++i)
|
2016-05-02 14:49:52 +00:00
|
|
|
workers.emplace_back(&thread_pool::loop, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
thread_pool::~thread_pool()
|
|
|
|
{
|
|
|
|
fin = true;
|
|
|
|
cond.notify_all();
|
|
|
|
|
|
|
|
for (auto& i : workers)
|
|
|
|
i.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename F, typename... Args>
|
|
|
|
auto thread_pool::add_task(F&& f, Args&&... args)
|
|
|
|
-> std::future<decltype(f(args...))>
|
|
|
|
{
|
|
|
|
auto pckgd_tsk = std::make_shared<std::packaged_task<decltype(f(args...))()> >
|
|
|
|
(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
|
|
|
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mu);
|
|
|
|
queue.emplace_back([pckgd_tsk](){ (*pckgd_tsk)(); });
|
|
|
|
}
|
|
|
|
cond.notify_one();
|
|
|
|
|
|
|
|
return pckgd_tsk->get_future();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename F, typename... Args>
|
|
|
|
auto thread_pool::priority_task(F&& f, Args&&... args)
|
|
|
|
-> std::future<decltype(f(args...))>
|
|
|
|
{
|
|
|
|
auto pckgd_tsk = std::make_shared<std::packaged_task<decltype(f(args...))()> >
|
|
|
|
(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
|
|
|
|
|
|
|
|
auto ret_val= pckgd_tsk->get_future();
|
|
|
|
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mu);
|
|
|
|
queue.emplace_front([pckgd_tsk](){ (*pckgd_tsk)(); });
|
|
|
|
}
|
|
|
|
cond.notify_one();
|
|
|
|
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename R>
|
|
|
|
void thread_pool::add_task(std::packaged_task<R()>& arg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mu);
|
|
|
|
queue.emplace_back([&arg](){ arg(); });
|
|
|
|
}
|
|
|
|
cond.notify_one();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename R>
|
|
|
|
void thread_pool::priority_task(std::packaged_task<R()>& arg)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mu);
|
|
|
|
queue.emplace_front([&arg](){ arg(); });
|
|
|
|
}
|
|
|
|
cond.notify_one();
|
|
|
|
}
|
|
|
|
|
|
|
|
void thread_pool::add_task(std::function<void()> func)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mu);
|
|
|
|
queue.push_back(std::move(func));
|
|
|
|
}
|
|
|
|
cond.notify_one();
|
|
|
|
}
|
|
|
|
|
|
|
|
void thread_pool::add_thread(size_t num = 1)
|
|
|
|
{
|
2016-11-12 09:55:22 +00:00
|
|
|
for (size_t i = 0; i < num; ++i)
|
2016-05-02 14:49:52 +00:00
|
|
|
{
|
|
|
|
workers.emplace_back(&thread_pool::loop, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void thread_pool::loop()
|
|
|
|
{
|
|
|
|
std::function<void()> fun;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(mu);
|
|
|
|
|
|
|
|
while (!fin && queue.empty())
|
|
|
|
cond.wait(lock);
|
|
|
|
|
|
|
|
if (fin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fun = std::move(queue.front());
|
|
|
|
queue.pop_front();
|
|
|
|
}
|
|
|
|
fun();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////// functions for testing & test zone ////////////////////
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
boost::multiprecision::cpp_int mp_factorial(unsigned long long int number)
|
|
|
|
{
|
|
|
|
boost::multiprecision::cpp_int num = number;
|
|
|
|
for (unsigned long long int i = number; i > 1; --i)
|
|
|
|
{
|
|
|
|
num *=(--number);
|
|
|
|
}
|
|
|
|
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
2016-05-02 15:38:08 +00:00
|
|
|
void simpleTask()
|
|
|
|
{
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
void function_vs_packaged_task_test(thread_pool &tp, const unsigned short TIMES = 10000)
|
2016-05-02 14:49:52 +00:00
|
|
|
{
|
|
|
|
namespace ch = std::chrono;
|
|
|
|
namespace mp = boost::multiprecision;
|
|
|
|
|
|
|
|
|
2016-05-02 15:38:08 +00:00
|
|
|
auto fstart = ch::high_resolution_clock::now();
|
|
|
|
for (int j = 0; j < TIMES; ++j)
|
|
|
|
{
|
|
|
|
tp.add_task(simpleTask);
|
|
|
|
}
|
|
|
|
auto fdur = ch::high_resolution_clock::now() - fstart;
|
|
|
|
|
|
|
|
auto pt = std::packaged_task<void()>(simpleTask);
|
|
|
|
auto pstart = ch::high_resolution_clock::now();
|
|
|
|
for (int j = 0; j < TIMES; ++j)
|
|
|
|
{
|
|
|
|
tp.add_task(pt);
|
|
|
|
}
|
|
|
|
auto pdur = ch::high_resolution_clock::now() - pstart;
|
|
|
|
|
|
|
|
std::cout << "Function: " << ch::duration_cast<ch::microseconds>(fdur).count() <<
|
|
|
|
std::endl << "Packaged task: " << ch::duration_cast<ch::microseconds>(pdur).count() << std::endl;
|
|
|
|
}
|
2016-05-02 14:49:52 +00:00
|
|
|
|
2016-05-02 15:38:08 +00:00
|
|
|
void factorial_test(thread_pool& tp, unsigned long long number)
|
|
|
|
{
|
|
|
|
std::packaged_task<boost::multiprecision::cpp_int()> pt(std::bind(mp_factorial, number-1));
|
|
|
|
tp.add_task(pt);
|
|
|
|
auto fu1 = tp.add_task(mp_factorial, number);
|
2016-05-02 14:49:52 +00:00
|
|
|
auto fu2 = pt.get_future();
|
|
|
|
|
2016-05-02 15:38:08 +00:00
|
|
|
std::cout << "Results for n & n-1: " << fu1.get() << " " << fu2.get() << std::endl;
|
|
|
|
std::cout << "With " << tp.get_thread_num() << " threads" << std::endl;
|
|
|
|
std::cout << "Type of future: " << boost::typeindex::type_id_with_cvr<decltype(fu1)>().pretty_name() << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void boost_vs_me_test(thread_pool& tp)
|
|
|
|
{ /* TODO */ }
|
|
|
|
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
{
|
|
|
|
thread_pool tp(4);
|
2016-05-02 14:49:52 +00:00
|
|
|
|
2016-05-02 15:38:08 +00:00
|
|
|
factorial_test(tp, 6);
|
|
|
|
function_vs_packaged_task_test(tp, 10000);
|
2016-05-02 14:49:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|