Date: Sat, 10 Jul 2021 11:23:16 -0300
C++'s threads library currently exposes the function
notify_all_at_thread_exit() to join detached threads through a
condition variable. Thus we can join on detached threads in the likes
of:
while (!ready) cv.wait(lk);
One scenario where cond-based join logic is more attractive than
thread::join() is to join multiple worker threads. Traditionally one
would:
std::vector<std::thread> workers;
for (/* ... */) workers.emplace_back(/* ... */);
// ...
for (auto& t: workers) t.join();
However when worker threads themselves can add even new threads to the
pool we have to constantly grow/shrink workers as they change.
Furthermore the variable workers would have to be protected through a
mutex. Condition variables make the implementation simpler and more
efficient:
std::size_t nworkers = 0;
// ...
std::unique_lock lk(nworkers_mtx);
while (nworkers > 0) workers_gone_cond.wait(lk);
Unfortunately the current featureset for C++ still adds some overhead
to this pattern. The worker threads will have to use code such as:
std::unique_lock lk(nworkers_mtx);
--nworkers;
std::notify_all_at_thread_exit(workers_gone_cond, std::move(lk);
This code will awake the waiting thread at every joined worker.
Ideally we should be able to write:
std::unique_lock lk(nworkers_mtx);
--nworkers;
if (nworkers == 0)
std::notify_all_at_thread_exit(workers_gone_cond, std::move(lk);
else
std::unlock_at_thread_exit(std::move(lk));
I think C++ should have std::unlock_at_thread_exit(). I have a
codebase affected by this issue as of now. My workaround is to use a
different dummy cond to avoid awaking the parent thread.
notify_all_at_thread_exit() to join detached threads through a
condition variable. Thus we can join on detached threads in the likes
of:
while (!ready) cv.wait(lk);
One scenario where cond-based join logic is more attractive than
thread::join() is to join multiple worker threads. Traditionally one
would:
std::vector<std::thread> workers;
for (/* ... */) workers.emplace_back(/* ... */);
// ...
for (auto& t: workers) t.join();
However when worker threads themselves can add even new threads to the
pool we have to constantly grow/shrink workers as they change.
Furthermore the variable workers would have to be protected through a
mutex. Condition variables make the implementation simpler and more
efficient:
std::size_t nworkers = 0;
// ...
std::unique_lock lk(nworkers_mtx);
while (nworkers > 0) workers_gone_cond.wait(lk);
Unfortunately the current featureset for C++ still adds some overhead
to this pattern. The worker threads will have to use code such as:
std::unique_lock lk(nworkers_mtx);
--nworkers;
std::notify_all_at_thread_exit(workers_gone_cond, std::move(lk);
This code will awake the waiting thread at every joined worker.
Ideally we should be able to write:
std::unique_lock lk(nworkers_mtx);
--nworkers;
if (nworkers == 0)
std::notify_all_at_thread_exit(workers_gone_cond, std::move(lk);
else
std::unlock_at_thread_exit(std::move(lk));
I think C++ should have std::unlock_at_thread_exit(). I have a
codebase affected by this issue as of now. My workaround is to use a
different dummy cond to avoid awaking the parent thread.
-- Vinícius dos Santos Oliveira https://vinipsmaker.github.io/
Received on 2021-07-10 09:24:10