| Index: src/Scheduler.cpp |
| =================================================================== |
| --- a/src/Scheduler.cpp |
| +++ b/src/Scheduler.cpp |
| @@ -27,7 +27,7 @@ |
| } |
| } |
| -void Scheduler::StartImmediatelyInSingleUseThread(std::function<void()> task) |
| +void StartImmediatelyInSingleUseDetachedThread(std::function<void()> task) |
| { |
| if (!task) |
| { |
| @@ -36,3 +36,82 @@ |
| auto th = std::thread(SingleUseThreadMain, task); |
| th.detach(); |
| } |
| + |
| +OperationRunner::OperationRunner() |
| + : isRunning(true) |
| +{ |
| + th = std::thread([this]() { ThreadMain(); }); |
| + /* |
| + * Note that we don't need to wait for our thread to start running |
| + * before we consider the object fully constructed. |
| + * Because of how the `wait()` statement in `ThreadMain` is written, |
| + * any operation arriving before the thread begins will be |
| + * processed immediately, without blocking on a notification. |
| + */ |
|
sergei
2017/01/20 13:08:13
That comment is good however I don't think we need
Eric
2017/03/30 17:16:58
Behavior is always clearer *after* you read the co
sergei
2017/04/03 15:35:32
I'm talking about implementation without race cond
|
| +} |
| + |
| +OperationRunner::~OperationRunner() |
| +{ |
| + // Shut down the operation queue |
| + Run([this]() { isRunning = false; }); |
| + th.join(); |
| +} |
| + |
| +void OperationRunner::Run(std::function<void()> f) |
| +{ |
| + UniqueLockType ul(m); |
| + queue.push(f); |
| + cv.notify_one(); |
| +} |
| + |
| +void OperationRunner::ThreadMain() |
| +{ |
| + while (true) |
| + { |
| + std::function<void()> op; |
| + { |
| + UniqueLockType ul(m); |
| + // Checking the flag here avoids needing a second lock object |
| + if (!isRunning) |
|
sergei
2017/01/20 13:08:13
Why not to put isRunning into while condition? we
Eric
2017/03/30 17:16:58
Because it's not correct. It opens up an opportuni
sergei
2017/04/03 15:35:31
Could you please explain when that race condition
|
| + { |
| + break; |
| + } |
| + cv.wait(ul, [this]() -> bool { return !queue.empty(); }); |
| + op = queue.front(); |
| + queue.pop(); |
| + } |
| + // Assert `m` is unlocked |
|
sergei
2017/01/20 13:08:13
I would remove that comment.
Eric
2017/03/30 17:16:58
Nope; it stays. Tracking when the mutex is locked
|
| + try |
| + { |
| + if (op) |
|
sergei
2017/04/03 15:35:31
Why not to prevent putting of empty functions in t
|
| + { |
| + op(); |
| + } |
| + } |
| + catch (std::exception& e) |
|
sergei
2017/04/03 15:35:32
it generates a warning about unused local variable
|
| + { |
| + // suppress |
| + } |
| + } |
| +} |
| + |
| +SingleUseWorker::SingleUseWorker() |
| +{} |
| + |
| +SingleUseWorker::~SingleUseWorker() |
| +{ |
| + if (th.joinable()) |
|
sergei
2017/01/20 13:08:13
This implementation with if (th.joinable()) should
Eric
2017/03/30 17:16:58
Yes, you need to be able to join the thread either
|
| + { |
| + th.join(); |
| + } |
| +} |
| + |
| +void SingleUseWorker::Run(std::function<void()> f) |
| +{ |
| + th = std::move(std::thread(f)); |
|
sergei
2017/01/20 13:08:13
Do we really need to explicitly say std::move here
Eric
2017/03/30 17:16:58
It avoids a second copy, since there's already one
sergei
2017/04/03 15:35:32
Copy of what? JIC, std::thread cannot be copied.
|
| +} |
| + |
| +void SingleUseWorker::Join() |
| +{ |
| + th.join(); |
| +} |