With a few experience of python
and golang
, I tried to make (simple) thread pool executor. Tasks of the executor must be copy-constructible std::function<void()>
and the result of tasks are required in the main thread.
My Header and Implementation File:
#include <iostream>
#include <queue>
#include <chrono>
#include <atomic>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <functional>
class PoolExecutor
{
public:
explicit PoolExecutor(const unsigned int pool_size)
: stop_(false)
, working_(0)
{
for(unsigned int i = 0; i < pool_size; ++i)
{
this->threads_.emplace_back(
std::thread(std::bind(&PoolExecutor::worker, this)));
}
}
virtual ~PoolExecutor()
{
this->stop_ = true;
for(auto& thread : this->threads_)
{
thread.join();
}
}
void submit(std::function<void()> func)
{
{
std::lock_guard<std::mutex> guard(this->mu2_);
this->work_queue_.push(func);
}
this->cv_.notify_all();
}
bool finished()
{
std::lock_guard<std::mutex> guard(this->mu2_);
return this->working_ == 0 && this->work_queue_.empty();
}
void stop()
{
this->stop_ = true;
}
private:
std::vector<std::thread> threads_;
std::shared_timed_mutex mu_;
std::mutex mu2_;
std::atomic_bool stop_;
std::atomic_int working_;
std::condition_variable_any cv_;
std::queue<std::function<void()> > work_queue_;
void worker()
{
std::function<void()> func;
while(!this->stop_)
{
using namespace std::literals::chrono_literals;
std::shared_lock<std::shared_timed_mutex> lock(this->mu_, std::try_to_lock);
this->cv_.wait_for(lock, 10ms);
bool accquired = false;
{
std::lock_guard<std::mutex> guard(this->mu2_);
if(!this->work_queue_.empty())
{
func = this->work_queue_.front();
this->work_queue_.pop();
accquired = true;
this->working_++;
}
}
if(accquired)
{
func();
--this->working_;
}
}
}
};
Sample Usage:
int main()
{
PoolExecutor executor(8);
std::mutex mu;
int sum = 0;
auto func = [&sum, &mu](const int i) {
std::cout << "work " << i << " try lock\n";
{
//simulate long running function
using namespace std::literals::chrono_literals;
std::lock_guard<std::mutex> guard(mu);
std::this_thread::sleep_for(10ms);
std::cout << "work " << i << " acquire lock\n";
sum += i;
}
};
for(auto i = 0; i < 100; ++i)
{
executor.submit(std::bind(func, i));
}
int count = 0;
while(!executor.finished())
{
using namespace std::literals::chrono_literals;
std::this_thread::sleep_for(200ms);
std::cout << "sleep count: " << ++count << "\n";
}
std::cout << "sum: " << sum << "\n";
return 0;
}
I've tested with sample code using MSVC 15
(also tested with shared_mutex
instead of shared_timed_mutex
) and clang 3.8
. The result is printed as expected, but I cannot sure what I did it.
Some questions.
Q1. Am I using mutex
and lock
correctly?
Q2. It is possible to use single mutex in PoolExecutor
class?
Q3. Is there any ways to wait executor until finished?