Added a simple threadsafe queue, and on top of that a simple thread pool
This commit is contained in:
parent
be6ee1ae4c
commit
cbbdf7d027
2 changed files with 106 additions and 0 deletions
67
include/lock_queue.hpp
Normal file
67
include/lock_queue.hpp
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Thread safe queue (with fine grain locks).
|
||||||
|
Based on the book by Williams.
|
||||||
|
No wait_and_pop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class queue {
|
||||||
|
private:
|
||||||
|
struct node {
|
||||||
|
std::shared_ptr<T> data;
|
||||||
|
std::unique_ptr<node> next;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex head_mutex;
|
||||||
|
std::unique_ptr<node> head;
|
||||||
|
|
||||||
|
std::mutex tail_mutex;
|
||||||
|
node* tail;
|
||||||
|
|
||||||
|
node* get_tail(){
|
||||||
|
std::lock_guard<std::mutex> lock(tail_mutex);
|
||||||
|
return tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<node> pop_head(){
|
||||||
|
std::lock_guard<std::mutex> lock(head_mutex);
|
||||||
|
|
||||||
|
if(head.get() == get_tail()){
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<node> old_head = std::move(head);
|
||||||
|
head = std::move(old_head->next);
|
||||||
|
return old_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
queue()
|
||||||
|
: head(new node)
|
||||||
|
, tail(head.get())
|
||||||
|
{}
|
||||||
|
|
||||||
|
queue(const queue &) = delete;
|
||||||
|
queue& operator=(const queue &) = delete;
|
||||||
|
|
||||||
|
std::shared_ptr<T> try_pop(){
|
||||||
|
std::unique_ptr<node> old_head = pop_head();
|
||||||
|
return old_head ? old_head->data : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(T value){
|
||||||
|
auto new_data = std::make_shared<T>(std::move(value));
|
||||||
|
std::unique_ptr<node> p(new node);
|
||||||
|
|
||||||
|
node* const new_tail = p.get();
|
||||||
|
std::lock_guard<std::mutex> lock(tail_mutex);
|
||||||
|
|
||||||
|
tail->data = new_data;
|
||||||
|
tail->next = std::move(p);
|
||||||
|
tail = new_tail;
|
||||||
|
}
|
||||||
|
};
|
39
include/thread_pool.hpp
Normal file
39
include/thread_pool.hpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Simple thread pool, no fancy stuff.
|
||||||
|
It is thread safe (i.e. concurrent adds).
|
||||||
|
Threads terminate if there is no work (we need wait_and_pop for that).
|
||||||
|
Works best if all work is added, and then run.
|
||||||
|
Doesn't care about exceptions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <thread>
|
||||||
|
#include "lock_queue.hpp"
|
||||||
|
|
||||||
|
struct thread_pool {
|
||||||
|
void add(std::function<void()> f){
|
||||||
|
work.push(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(int number_of_threads){
|
||||||
|
for(int i = 0; i < number_of_threads; ++i){
|
||||||
|
threads.emplace_back([this]{
|
||||||
|
auto x = work.try_pop();
|
||||||
|
while(x){
|
||||||
|
(*x)();
|
||||||
|
x = work.try_pop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~thread_pool(){
|
||||||
|
for(auto & t : threads) t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
queue<std::function<void()>> work;
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
};
|
Reference in a new issue