Commit 194b7732 authored by Giuseppe Ottaviano's avatar Giuseppe Ottaviano Committed by Facebook GitHub Bot

Simplify ThreadedExecutor

Summary: The implementation doesn't need explicit locking or condition variables, everything can be done with a single concurrent queue.

Reviewed By: yfeldblum

Differential Revision: D25919429

fbshipit-source-id: a2b1e6332305174ba41d02fd79d9525a12b82218
parent 6a6ea7ca
...@@ -17,65 +17,43 @@ ...@@ -17,65 +17,43 @@
#include <folly/executors/ThreadedExecutor.h> #include <folly/executors/ThreadedExecutor.h>
#include <chrono> #include <chrono>
#include <utility>
#include <glog/logging.h> #include <glog/logging.h>
#include <folly/ScopeGuard.h>
#include <folly/executors/thread_factory/NamedThreadFactory.h> #include <folly/executors/thread_factory/NamedThreadFactory.h>
#include <folly/system/ThreadName.h> #include <folly/system/ThreadName.h>
namespace folly { namespace folly {
template <typename F>
static auto with_unique_lock(std::mutex& m, F&& f) -> decltype(f()) {
std::unique_lock<std::mutex> lock(m);
return f();
}
ThreadedExecutor::ThreadedExecutor(std::shared_ptr<ThreadFactory> threadFactory) ThreadedExecutor::ThreadedExecutor(std::shared_ptr<ThreadFactory> threadFactory)
: threadFactory_(std::move(threadFactory)) { : threadFactory_(std::move(threadFactory)),
controlt_ = std::thread([this] { control(); }); controlThread_([this] { control(); }) {}
}
ThreadedExecutor::~ThreadedExecutor() { ThreadedExecutor::~ThreadedExecutor() {
stopping_.store(true, std::memory_order_release); stopping_.store(true, std::memory_order_release);
notify(); controlMessages_.enqueue({Message::Type::StopControl, {}, {}});
controlt_.join(); controlThread_.join();
CHECK(running_.empty()); CHECK(running_.empty());
CHECK(finished_.empty()); CHECK(controlMessages_.empty());
} }
void ThreadedExecutor::add(Func func) { void ThreadedExecutor::add(Func func) {
CHECK(!stopping_.load(std::memory_order_acquire)); CHECK(!stopping_.load(std::memory_order_acquire));
with_unique_lock(enqueuedm_, [&] { enqueued_.push_back(std::move(func)); }); controlMessages_.enqueue({Message::Type::Start, std::move(func), {}});
notify();
} }
std::shared_ptr<ThreadFactory> ThreadedExecutor::newDefaultThreadFactory() { std::shared_ptr<ThreadFactory> ThreadedExecutor::newDefaultThreadFactory() {
return std::make_shared<NamedThreadFactory>("Threaded"); return std::make_shared<NamedThreadFactory>("Threaded");
} }
void ThreadedExecutor::notify() {
with_unique_lock(controlm_, [&] { controls_ = true; });
controlc_.notify_one();
}
void ThreadedExecutor::control() {
folly::setThreadName("ThreadedCtrl");
auto looping = true;
while (looping) {
controlWait();
looping = controlPerformAll();
}
}
void ThreadedExecutor::controlWait() {
constexpr auto kMaxWait = std::chrono::seconds(10);
std::unique_lock<std::mutex> lock(controlm_);
controlc_.wait_for(lock, kMaxWait, [&] { return controls_; });
controls_ = false;
}
void ThreadedExecutor::work(Func& func) { void ThreadedExecutor::work(Func& func) {
SCOPE_EXIT {
controlMessages_.enqueue(
{Message::Type::Join, {}, std::this_thread::get_id()});
};
try { try {
func(); func();
} catch (const std::exception& e) { } catch (const std::exception& e) {
...@@ -84,35 +62,34 @@ void ThreadedExecutor::work(Func& func) { ...@@ -84,35 +62,34 @@ void ThreadedExecutor::work(Func& func) {
} catch (...) { } catch (...) {
LOG(ERROR) << "ThreadedExecutor: func threw unhandled non-exception object"; LOG(ERROR) << "ThreadedExecutor: func threw unhandled non-exception object";
} }
auto id = std::this_thread::get_id();
with_unique_lock(finishedm_, [&] { finished_.push_back(id); });
notify();
}
void ThreadedExecutor::controlJoinFinishedThreads() {
std::deque<std::thread::id> finishedt;
with_unique_lock(finishedm_, [&] { std::swap(finishedt, finished_); });
for (auto id : finishedt) {
running_[id].join();
running_.erase(id);
}
} }
void ThreadedExecutor::controlLaunchEnqueuedTasks() { void ThreadedExecutor::control() {
std::deque<Func> enqueuedt; folly::setThreadName("ThreadedCtrl");
with_unique_lock(enqueuedm_, [&] { std::swap(enqueuedt, enqueued_); }); bool controlStopping = false;
for (auto& f : enqueuedt) { while (!(controlStopping && running_.empty())) {
auto msg = controlMessages_.dequeue();
switch (msg.type) {
case Message::Type::Start: {
auto th = threadFactory_->newThread( auto th = threadFactory_->newThread(
[this, f = std::move(f)]() mutable { work(f); }); [this, func = std::move(msg.startFunc)]() mutable { work(func); });
auto id = th.get_id(); auto id = th.get_id();
running_[id] = std::move(th); running_[id] = std::move(th);
break;
}
case Message::Type::Join: {
auto it = running_.find(msg.joinTid);
CHECK(it != running_.end());
it->second.join();
running_.erase(it);
break;
}
case Message::Type::StopControl: {
CHECK(!std::exchange(controlStopping, true));
break;
}
}
} }
} }
bool ThreadedExecutor::controlPerformAll() {
auto stopping = stopping_.load(std::memory_order_acquire);
controlJoinFinishedThreads();
controlLaunchEnqueuedTasks();
return !stopping || !running_.empty();
}
} // namespace folly } // namespace folly
...@@ -17,14 +17,12 @@ ...@@ -17,14 +17,12 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <condition_variable>
#include <deque>
#include <map>
#include <memory> #include <memory>
#include <mutex>
#include <thread> #include <thread>
#include <folly/Executor.h> #include <folly/Executor.h>
#include <folly/concurrency/UnboundedQueue.h>
#include <folly/container/F14Map.h>
#include <folly/executors/thread_factory/ThreadFactory.h> #include <folly/executors/thread_factory/ThreadFactory.h>
namespace folly { namespace folly {
...@@ -67,33 +65,28 @@ class ThreadedExecutor : public virtual folly::Executor { ...@@ -67,33 +65,28 @@ class ThreadedExecutor : public virtual folly::Executor {
void add(Func func) override; void add(Func func) override;
private: private:
static std::shared_ptr<ThreadFactory> newDefaultThreadFactory(); // TODO(ott): Switch to std::variant when available everywhere.
struct Message {
enum class Type { Start, Join, StopControl };
Type type;
Func startFunc;
std::thread::id joinTid;
};
void notify(); static std::shared_ptr<ThreadFactory> newDefaultThreadFactory();
void control();
void controlWait();
bool controlPerformAll();
void controlJoinFinishedThreads();
void controlLaunchEnqueuedTasks();
void work(Func& func); void work(Func& func);
void control();
std::shared_ptr<ThreadFactory> threadFactory_; std::shared_ptr<ThreadFactory> threadFactory_;
std::atomic<bool> stopping_{false}; std::atomic<bool> stopping_{false};
std::mutex controlm_; UMPSCQueue<Message, /* MayBlock */ true> controlMessages_;
std::condition_variable controlc_; std::thread controlThread_;
bool controls_ = false;
std::thread controlt_;
std::mutex enqueuedm_;
std::deque<Func> enqueued_;
// Accessed only by the control thread, so no synchronization. // Accessed only by the control thread, so no synchronization.
std::map<std::thread::id, std::thread> running_; F14FastMap<std::thread::id, std::thread> running_;
std::mutex finishedm_;
std::deque<std::thread::id> finished_;
}; };
} // namespace folly } // namespace folly
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment