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