mirror of https://github.com/JAJames/jessilib.git
Jessica James
7 years ago
11 changed files with 396 additions and 19 deletions
@ -0,0 +1,159 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2018 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#include "thread_pool.hpp" |
||||
|
#include <cassert> |
||||
|
|
||||
|
namespace jessilib { |
||||
|
|
||||
|
// thread_pool
|
||||
|
|
||||
|
thread_pool::thread_pool() |
||||
|
: thread_pool{ std::thread::hardware_concurrency() } { |
||||
|
} |
||||
|
|
||||
|
thread_pool::thread_pool(size_t in_threads) |
||||
|
: m_threads{ in_threads } { |
||||
|
assert(in_threads != 0); |
||||
|
|
||||
|
while (in_threads != 0) { |
||||
|
thread& self = m_threads[--in_threads]; |
||||
|
self.m_thread = std::thread([this, &self]() { |
||||
|
while (true) { |
||||
|
// Run next pending task, if there is any
|
||||
|
self.m_task = pop_task(); |
||||
|
if (self.m_task != nullptr) { |
||||
|
self.run_task(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (self.m_shutdown) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
// Push inactive thread
|
||||
|
{ |
||||
|
std::lock_guard<std::mutex> guard(m_inactive_threads_mutex); |
||||
|
m_inactive_threads.push(&self); |
||||
|
} |
||||
|
|
||||
|
// Wait for notification
|
||||
|
self.wait(); |
||||
|
|
||||
|
// Run task
|
||||
|
self.run_task(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename T> |
||||
|
T lock_helper(T&& in_obj, std::mutex& in_mutex) { |
||||
|
std::lock_guard<std::mutex> guard(in_mutex); |
||||
|
return std::move(in_obj); |
||||
|
} |
||||
|
|
||||
|
thread_pool::~thread_pool() { |
||||
|
join(); |
||||
|
} |
||||
|
|
||||
|
void thread_pool::push(task_t in_task) { |
||||
|
thread* target_thread = inactive_thread(); |
||||
|
|
||||
|
if (target_thread != nullptr) { |
||||
|
target_thread->m_task = in_task; |
||||
|
target_thread->m_notifier.notify_one(); |
||||
|
} |
||||
|
else { |
||||
|
std::lock_guard<std::mutex> guard(m_tasks_mutex); |
||||
|
m_tasks.push(in_task); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void thread_pool::join() { |
||||
|
std::lock_guard<std::mutex> guard(m_threads_mutex); |
||||
|
|
||||
|
// Join threads
|
||||
|
for (thread& thread : m_threads) { |
||||
|
// Mark thread for shutdown
|
||||
|
thread.m_shutdown = true; |
||||
|
thread.m_notifier.notify_one(); |
||||
|
|
||||
|
// Wait for thread to complete
|
||||
|
thread.m_thread.join(); |
||||
|
} |
||||
|
|
||||
|
// Cleanup threads
|
||||
|
m_threads.clear(); |
||||
|
while (!m_inactive_threads.empty()) { |
||||
|
m_inactive_threads.pop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
size_t thread_pool::threads() const { |
||||
|
//std::lock_guard<std::mutex> guard(m_threads_mutex);
|
||||
|
return m_threads.size(); |
||||
|
} |
||||
|
|
||||
|
size_t thread_pool::active() const { |
||||
|
std::lock_guard<std::mutex> guard(m_inactive_threads_mutex); |
||||
|
return threads() - m_inactive_threads.size(); |
||||
|
} |
||||
|
|
||||
|
// thread_pool private functions
|
||||
|
|
||||
|
thread_pool::thread* thread_pool::inactive_thread() { |
||||
|
std::lock_guard<std::mutex> guard(m_inactive_threads_mutex); |
||||
|
|
||||
|
if (!m_inactive_threads.empty()) { |
||||
|
thread* result = m_inactive_threads.front(); |
||||
|
m_inactive_threads.pop(); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
thread_pool::task_t thread_pool::pop_task() { |
||||
|
std::lock_guard<std::mutex> guard(m_tasks_mutex); |
||||
|
if (!m_tasks.empty()) { |
||||
|
task_t task = m_tasks.front(); |
||||
|
m_tasks.pop(); |
||||
|
return task; |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
// thread_pool::thread
|
||||
|
|
||||
|
void thread_pool::thread::run_task() { |
||||
|
if (m_task) { |
||||
|
m_active = true; |
||||
|
m_task(); |
||||
|
m_task = nullptr; |
||||
|
m_active = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void thread_pool::thread::wait() { |
||||
|
std::unique_lock<std::mutex> guard(m_notifier_mutex); |
||||
|
m_notifier.wait(guard); |
||||
|
} |
||||
|
|
||||
|
} // namespace jessilib
|
@ -0,0 +1,73 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2018 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <cstdint> |
||||
|
#include <functional> |
||||
|
#include <vector> |
||||
|
#include <thread> |
||||
|
#include <queue> |
||||
|
#include <atomic> |
||||
|
#include <mutex> |
||||
|
#include <condition_variable> |
||||
|
|
||||
|
namespace jessilib { |
||||
|
|
||||
|
class thread_pool { |
||||
|
public: |
||||
|
// Types
|
||||
|
using task_t = std::function<void(void)>; |
||||
|
|
||||
|
thread_pool(); |
||||
|
thread_pool(size_t in_threads); |
||||
|
thread_pool(const thread_pool&) = delete; |
||||
|
thread_pool(thread_pool&&) = delete; |
||||
|
~thread_pool(); |
||||
|
|
||||
|
void push(task_t in_task); // push a task to the pool
|
||||
|
void join(); // join all threads
|
||||
|
size_t threads() const; // how many threads are in the pool
|
||||
|
size_t active() const; // how many threads are running tasks
|
||||
|
|
||||
|
private: |
||||
|
struct thread { |
||||
|
void run_task(); |
||||
|
void wait(); |
||||
|
|
||||
|
std::atomic<bool> m_active{ false }; |
||||
|
std::atomic<bool> m_shutdown{ false }; |
||||
|
task_t m_task; |
||||
|
std::condition_variable m_notifier; |
||||
|
std::mutex m_notifier_mutex; |
||||
|
std::thread m_thread; |
||||
|
}; |
||||
|
|
||||
|
thread* inactive_thread(); |
||||
|
task_t pop_task(); |
||||
|
|
||||
|
std::vector<thread> m_threads; |
||||
|
std::queue<thread*> m_inactive_threads; |
||||
|
std::queue<task_t> m_tasks; |
||||
|
|
||||
|
mutable std::mutex m_threads_mutex; |
||||
|
mutable std::mutex m_inactive_threads_mutex; |
||||
|
mutable std::mutex m_tasks_mutex; |
||||
|
}; // class thread_pool
|
||||
|
|
||||
|
} // namespace jessilib
|
@ -0,0 +1,25 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2018 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
// Common includes
|
||||
|
#include "gtest/gtest.h" |
||||
|
|
||||
|
// Helper macros
|
||||
|
#define repeat( ITERATIONS ) for (size_t iteration__ = 0; iteration__ != (ITERATIONS); ++iteration__) |
@ -0,0 +1,103 @@ |
|||||
|
/**
|
||||
|
* Copyright (C) 2018 Jessica James. |
||||
|
* |
||||
|
* Permission to use, copy, modify, and/or distribute this software for any |
||||
|
* purpose with or without fee is hereby granted, provided that the above |
||||
|
* copyright notice and this permission notice appear in all copies. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||||
|
* |
||||
|
* Written by Jessica James <jessica.aj@outlook.com> |
||||
|
*/ |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include "test.hpp" |
||||
|
#include "thread_pool.hpp" |
||||
|
|
||||
|
using namespace jessilib; |
||||
|
using namespace std::literals; |
||||
|
|
||||
|
constexpr size_t total_iterations{ 100 }; |
||||
|
|
||||
|
TEST(ThreadPoolTest, initialDefault) { |
||||
|
thread_pool pool; |
||||
|
std::this_thread::sleep_for(10ms); |
||||
|
|
||||
|
EXPECT_EQ(pool.threads(), std::thread::hardware_concurrency()); |
||||
|
std::this_thread::sleep_for(10ms); |
||||
|
EXPECT_EQ(pool.active(), 0); |
||||
|
|
||||
|
pool.join(); |
||||
|
|
||||
|
EXPECT_EQ(pool.active(), 0); |
||||
|
EXPECT_EQ(pool.threads(), 0); |
||||
|
} |
||||
|
|
||||
|
TEST(ThreadPoolTest, initialSizeDefined) { |
||||
|
thread_pool pool{ 7 }; |
||||
|
|
||||
|
EXPECT_EQ(pool.threads(), 7); |
||||
|
std::this_thread::sleep_for(10ms); |
||||
|
EXPECT_EQ(pool.active(), 0); |
||||
|
|
||||
|
pool.join(); |
||||
|
|
||||
|
EXPECT_EQ(pool.active(), 0); |
||||
|
EXPECT_EQ(pool.threads(), 0); |
||||
|
} |
||||
|
|
||||
|
TEST(ThreadPoolTest, push) { |
||||
|
std::atomic<size_t> iterations{ 0 }; |
||||
|
thread_pool pool; |
||||
|
|
||||
|
repeat (total_iterations) { |
||||
|
pool.push([&iterations, &pool]() { |
||||
|
++iterations; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
pool.join(); |
||||
|
EXPECT_EQ(iterations, total_iterations); |
||||
|
} |
||||
|
|
||||
|
TEST(ThreadPoolTest, deadlockSingleThread) { |
||||
|
std::atomic<size_t> iterations{ 0 }; |
||||
|
thread_pool pool{ 1 }; |
||||
|
|
||||
|
repeat (total_iterations) { |
||||
|
pool.push([&iterations, &pool]() { |
||||
|
++iterations; |
||||
|
|
||||
|
// Neither of the below should cause a deadlock
|
||||
|
EXPECT_EQ(pool.threads(), 1); |
||||
|
EXPECT_EQ(pool.active(), 1); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
pool.join(); |
||||
|
EXPECT_EQ(iterations, total_iterations); |
||||
|
} |
||||
|
|
||||
|
TEST(ThreadPoolTest, deadlockMultiThread) { |
||||
|
std::atomic<size_t> iterations{ 0 }; |
||||
|
thread_pool pool{ 8 }; |
||||
|
|
||||
|
repeat (total_iterations) { |
||||
|
pool.push([&iterations, &pool]() { |
||||
|
++iterations; |
||||
|
|
||||
|
// Neither of the below should cause a deadlock
|
||||
|
pool.threads(); |
||||
|
pool.active(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
pool.join(); |
||||
|
EXPECT_EQ(iterations, total_iterations); |
||||
|
} |
Loading…
Reference in new issue