New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suspend thread pool #3080
Suspend thread pool #3080
Changes from 20 commits
9c8e2d2
9fef6b4
9bde104
8946ed6
1009777
d808cda
a884c5a
966a96a
3f67f0c
58b7366
d1e0fcd
dcbb24f
85d8041
dc3d5ea
9c90a2f
62eee0a
3ff81cb
f7bab55
cd4e4d8
305c769
027d260
cb71087
e1704ff
25a1ee5
4687e72
ec97d9e
0273adc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,11 +6,14 @@ | |
#if !defined(HPX_SCHEDULED_THREAD_POOL_IMPL_HPP) | ||
#define HPX_SCHEDULED_THREAD_POOL_IMPL_HPP | ||
|
||
#include <hpx/apply.hpp> | ||
#include <hpx/compat/barrier.hpp> | ||
#include <hpx/compat/mutex.hpp> | ||
#include <hpx/compat/thread.hpp> | ||
#include <hpx/exception.hpp> | ||
#include <hpx/exception_info.hpp> | ||
#include <hpx/lcos/future.hpp> | ||
#include <hpx/lcos/local/packaged_task.hpp> | ||
#include <hpx/runtime/resource/detail/partitioner.hpp> | ||
#include <hpx/runtime/threads/detail/create_thread.hpp> | ||
#include <hpx/runtime/threads/detail/create_work.hpp> | ||
|
@@ -27,6 +30,7 @@ | |
#include <hpx/state.hpp> | ||
#include <hpx/throw_exception.hpp> | ||
#include <hpx/util/assert.hpp> | ||
#include <hpx/util/detail/yield_k.hpp> | ||
#include <hpx/util/unlock_guard.hpp> | ||
|
||
#include <boost/system/system_error.hpp> | ||
|
@@ -316,13 +320,81 @@ namespace hpx { namespace threads { namespace detail | |
} | ||
|
||
template <typename Scheduler> | ||
void scheduled_thread_pool<Scheduler>::resume(error_code& ec) | ||
template <typename F> | ||
void scheduled_thread_pool<Scheduler>::suspend_func(F&& callback, error_code& ec) | ||
{ | ||
if (!(mode_ & threads::policies::enable_elasticity)) | ||
for (std::size_t k = 0; | ||
sched_->Scheduler::get_thread_count() > | ||
get_background_thread_count(); | ||
++k) | ||
{ | ||
util::detail::yield_k(k, "scheduled_thread_pool::suspend_func"); | ||
} | ||
|
||
for (std::size_t i = 0; i != threads_.size(); ++i) | ||
{ | ||
suspend_processing_unit_internal(i, ec); | ||
} | ||
|
||
callback(); | ||
} | ||
|
||
template <typename Scheduler> | ||
template <typename F> | ||
void scheduled_thread_pool<Scheduler>::suspend_internal(F&& callback, error_code& ec) | ||
{ | ||
if (threads::get_self_ptr() && hpx::this_thread::get_pool() == this) | ||
{ | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::suspend", | ||
"cannot suspend a pool from itself"); | ||
return; | ||
} | ||
|
||
auto suspend_func_wrapper = | ||
[this, callback{std::move(callback)}, ec]() mutable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is C++14 only. We try to stay compatible with C++11. Please use the (new) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I'm aware. Again needs tidying up. Sorry, should've been more explicit about what's still to do. Thanks for pointing to the macro, I wasn't quite sure how to do this in a C++11 way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In C++11 we just copy into the capture :/ The only alternative is to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had problems with the |
||
{ | ||
this->suspend_func(callback, ec); | ||
}; | ||
|
||
if (threads::get_self_ptr()) | ||
{ | ||
hpx::apply(std::move(suspend_func_wrapper)); | ||
} | ||
else | ||
{ | ||
compat::thread(std::move(suspend_func_wrapper)).detach(); | ||
} | ||
} | ||
|
||
template <typename Scheduler> | ||
future<void> scheduled_thread_pool<Scheduler>::suspend(error_code& ec) | ||
{ | ||
if (!threads::get_self_ptr()) | ||
{ | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::suspend", | ||
"cannot call suspend from outside HPX, use suspend_cb instead"); | ||
return make_ready_future(); | ||
} | ||
|
||
lcos::local::packaged_task<void(void)> pt([](){}); | ||
hpx::future fut = pt.get_future(); | ||
|
||
suspend_internal(std::move(pt), ec); | ||
|
||
return fut; | ||
} | ||
|
||
template <typename Scheduler> | ||
void scheduled_thread_pool<Scheduler>::suspend_cb(std::function<void(void)> callback, error_code& ec) | ||
{ | ||
suspend_internal(callback, ec); | ||
} | ||
|
||
template <typename Scheduler> | ||
void scheduled_thread_pool<Scheduler>::resume(error_code& ec) | ||
{ | ||
for (std::size_t i = 0; i != threads_.size(); ++i) | ||
{ | ||
resume_processing_unit_internal(i, ec); | ||
|
@@ -439,8 +511,9 @@ namespace hpx { namespace threads { namespace detail | |
// the OS thread is allowed to exit only if no more HPX | ||
// threads exist or if some other thread has terminated | ||
HPX_ASSERT( | ||
!sched_->Scheduler::get_thread_count( | ||
suspended, thread_priority_default, thread_num) || | ||
(sched_->Scheduler::get_thread_count( | ||
suspended, thread_priority_default, thread_num) == 0 && | ||
sched_->Scheduler::get_queue_length(thread_num) == 0) || | ||
sched_->Scheduler::get_state(thread_num) > state_stopping); | ||
} | ||
catch (hpx::exception const& e) | ||
|
@@ -1366,21 +1439,22 @@ namespace hpx { namespace threads { namespace detail | |
std::size_t virt_core, std::size_t thread_num, | ||
std::shared_ptr<compat::barrier> startup, error_code& ec) | ||
{ | ||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
if (threads_.size() <= virt_core) | ||
threads_.resize(virt_core + 1); | ||
|
||
if (threads_[virt_core].joinable()) | ||
{ | ||
l.unlock(); | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::add_processing_unit", | ||
"the given virtual core has already been added to this " | ||
"thread pool"); | ||
return; | ||
} | ||
|
||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
resource::get_partitioner().assign_pu(id_.name(), virt_core); | ||
|
||
std::atomic<hpx::state>& state = | ||
|
@@ -1435,20 +1509,19 @@ namespace hpx { namespace threads { namespace detail | |
void scheduled_thread_pool<Scheduler>::remove_processing_unit_internal( | ||
std::size_t virt_core, error_code& ec) | ||
{ | ||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
if (threads_.size() <= virt_core || !threads_[virt_core].joinable()) | ||
{ | ||
l.unlock(); | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::remove_processing_unit", | ||
"the given virtual core has already been stopped to run on " | ||
"this thread pool"); | ||
return; | ||
} | ||
|
||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
compat::thread t; | ||
|
||
std::atomic<hpx::state>& state = | ||
sched_->Scheduler::get_state(virt_core); | ||
|
||
|
@@ -1460,13 +1533,19 @@ namespace hpx { namespace threads { namespace detail | |
oldstate == state_stopped); | ||
|
||
resource::get_partitioner().unassign_pu(id_.name(), virt_core); | ||
compat::thread t; | ||
std::swap(threads_[virt_core], t); | ||
|
||
if (threads::get_self_ptr()) | ||
{ | ||
while (virt_core == hpx::get_worker_thread_num()) | ||
std::size_t thread_num = thread_offset_ + virt_core; | ||
|
||
for (std::size_t k = 0; | ||
thread_num == hpx::get_worker_thread_num(); | ||
++k) | ||
{ | ||
hpx::this_thread::suspend(); | ||
util::detail::yield_k(k, | ||
"scheduled_thread_pool::remove_processing_unit_internal"); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop looks like to be a good candidate for being extracted into a utility function (similar construct are used during shutdown as well, iirc). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. The loops at shutdown are at least similar, I'll see what common functionality I can get out of there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've pulled out a |
||
|
||
|
@@ -1477,41 +1556,51 @@ namespace hpx { namespace threads { namespace detail | |
void scheduled_thread_pool<Scheduler>::suspend_processing_unit_internal( | ||
std::size_t virt_core, error_code& ec) | ||
{ | ||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
if (threads_.size() <= virt_core || !threads_[virt_core].joinable()) | ||
{ | ||
l.unlock(); | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::suspend_processing_unit", | ||
"the given virtual core has already been stopped to run on " | ||
"this thread pool"); | ||
return; | ||
} | ||
|
||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
// inform the scheduler to suspend the virtual core | ||
std::atomic<hpx::state>& state = | ||
sched_->Scheduler::get_state(virt_core); | ||
|
||
// check if already suspended | ||
hpx::state current_state = state.load(); | ||
if (current_state == state_pre_sleep || current_state == state_sleeping) | ||
{ | ||
return; | ||
} | ||
|
||
hpx::state oldstate = state.exchange(state_pre_sleep); | ||
|
||
HPX_ASSERT(oldstate == state_running); | ||
|
||
if (threads::get_self_ptr()) | ||
{ | ||
while (virt_core == hpx::get_worker_thread_num()) | ||
{ | ||
hpx::this_thread::suspend(); | ||
} | ||
std::size_t thread_num = thread_offset_ + virt_core; | ||
|
||
while (state.load() == state_pre_sleep) | ||
for (std::size_t k = 0; | ||
thread_num == hpx::get_worker_thread_num(); | ||
++k) | ||
{ | ||
hpx::this_thread::suspend(); | ||
util::detail::yield_k(k, | ||
"scheduled_thread_pool::suspend_processing_unit_internal"); | ||
} | ||
} | ||
else | ||
|
||
for (std::size_t k = 0; state.load() == state_pre_sleep; ++k) | ||
{ | ||
while (state.load() == state_pre_sleep) {} | ||
util::detail::yield_k(k, | ||
"scheduled_thread_pool::suspend_processing_unit_internal"); | ||
} | ||
} | ||
|
||
|
@@ -1535,39 +1624,27 @@ namespace hpx { namespace threads { namespace detail | |
void scheduled_thread_pool<Scheduler>::resume_processing_unit_internal( | ||
std::size_t virt_core, error_code& ec) | ||
{ | ||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
if (threads_.size() <= virt_core || !threads_[virt_core].joinable()) | ||
{ | ||
l.unlock(); | ||
HPX_THROWS_IF(ec, bad_parameter, | ||
"scheduled_thread_pool<Scheduler>::suspend_processing_unit", | ||
"the given virtual core has already been stopped to run on " | ||
"this thread pool"); | ||
return; | ||
} | ||
|
||
std::unique_lock<compat::mutex> | ||
l(sched_->Scheduler::get_pu_mutex(virt_core)); | ||
|
||
std::atomic<hpx::state>& state = | ||
sched_->Scheduler::get_state(virt_core); | ||
|
||
if (threads::get_self_ptr()) | ||
for (std::size_t k = 0; state.load() == state_sleeping; ++k) | ||
{ | ||
sched_->Scheduler::resume(virt_core); | ||
|
||
while (state.load() == state_sleeping) | ||
{ | ||
sched_->Scheduler::resume(virt_core); | ||
hpx::this_thread::suspend(); | ||
} | ||
} | ||
else | ||
{ | ||
sched_->Scheduler::resume(virt_core); | ||
|
||
while (state.load() == state_sleeping) | ||
{ | ||
sched_->Scheduler::resume(virt_core); | ||
} | ||
util::detail::yield_k(k, | ||
"scheduled_thread_pool::resume_processing_unit_internal"); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API returning a future does not need to take an
error_code
as all possible exceptions will be reported through the future mechanism. Do I miss something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you're missing anything. This part of what I meant when I said it needs tidying up (just wanted to have the basics working first to try it out).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. Things start looking very good now. Thanks for your work on this!