ccls/src/threaded_queue.h

189 lines
5.1 KiB
C
Raw Normal View History

2017-03-25 19:18:25 +00:00
#pragma once
2017-03-25 20:32:44 +00:00
#include <optional.h>
2017-09-22 01:14:57 +00:00
#include "work_thread.h"
2017-03-25 19:18:25 +00:00
#include <algorithm>
#include <atomic>
2017-03-25 19:18:25 +00:00
#include <condition_variable>
2017-09-22 01:14:57 +00:00
#include <mutex>
#include <queue>
2017-09-14 07:22:06 +00:00
using std::experimental::nullopt;
2017-09-22 01:14:57 +00:00
using std::experimental::optional;
2017-09-14 07:22:06 +00:00
2017-03-25 20:32:44 +00:00
// TODO: cleanup includes.
struct BaseThreadQueue {
virtual bool IsEmpty() = 0;
virtual ~BaseThreadQueue() = default;
};
struct MultiQueueWaiter {
std::mutex m;
std::condition_variable cv;
bool HasState(std::initializer_list<BaseThreadQueue*> queues) {
for (BaseThreadQueue* queue : queues) {
if (!queue->IsEmpty())
return true;
}
return false;
}
void Wait(std::initializer_list<BaseThreadQueue*> queues) {
// We cannot have a single condition variable wait on all of the different
// mutexes, so we have a global condition variable that every queue will
// notify. After it is notified we check to see if any of the queues have
// data; if they do, we return.
//
// We repoll every 5 seconds because it's not possible to atomically check
// the state of every queue and then setup the condition variable. So, if
// Wait() is called, HasState() returns false, and then in the time after
// HasState() is called data gets posted but before we begin waiting for
// the condition variable, we will miss the notification. The timeout of 5
// means that if this happens we will delay operation for 5 seconds.
2017-09-13 03:35:27 +00:00
//
// If we're trying to exit (WorkThread::request_exit_on_idle), do not
// bother waiting.
2017-09-13 03:35:27 +00:00
while (!HasState(queues) && !WorkThread::request_exit_on_idle) {
std::unique_lock<std::mutex> l(m);
cv.wait_for(l, std::chrono::seconds(5));
}
}
};
2017-03-25 19:18:25 +00:00
// A threadsafe-queue. http://stackoverflow.com/a/16075550
template <class T>
struct ThreadedQueue : public BaseThreadQueue {
2017-09-22 01:14:57 +00:00
public:
2017-10-31 19:37:52 +00:00
ThreadedQueue() : total_count_(0) {
owned_waiter_ = MakeUnique<MultiQueueWaiter>();
waiter_ = owned_waiter_.get();
}
2017-10-31 19:37:52 +00:00
explicit ThreadedQueue(MultiQueueWaiter* waiter)
: total_count_(0), waiter_(waiter) {}
2017-10-31 19:37:52 +00:00
// Returns the number of elements in the queue. This is lock-free.
size_t Size() const { return total_count_; }
2017-10-25 07:12:11 +00:00
// Add an element to the front of the queue.
void PriorityEnqueue(T&& t) {
std::lock_guard<std::mutex> lock(mutex_);
priority_.push(std::move(t));
++total_count_;
waiter_->cv.notify_all();
}
2017-03-25 19:18:25 +00:00
// Add an element to the queue.
void Enqueue(T&& t) {
2017-03-25 19:18:25 +00:00
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(std::move(t));
++total_count_;
waiter_->cv.notify_all();
2017-03-25 19:18:25 +00:00
}
// Add a set of elements to the queue.
void EnqueueAll(std::vector<T>&& elements) {
std::lock_guard<std::mutex> lock(mutex_);
2017-10-31 19:37:52 +00:00
total_count_ += elements.size();
for (T& element : elements) {
queue_.push(std::move(element));
}
elements.clear();
2017-10-31 19:37:52 +00:00
waiter_->cv.notify_all();
}
// Return all elements in the queue.
std::vector<T> DequeueAll() {
std::lock_guard<std::mutex> lock(mutex_);
2017-10-31 19:37:52 +00:00
total_count_ = 0;
std::vector<T> result;
result.reserve(priority_.size() + queue_.size());
while (!priority_.empty()) {
result.emplace_back(std::move(priority_.front()));
priority_.pop();
}
while (!queue_.empty()) {
result.emplace_back(std::move(queue_.front()));
queue_.pop();
}
2017-10-31 19:37:52 +00:00
return result;
}
2017-10-31 19:37:52 +00:00
// Returns true if the queue is empty. This is lock-free.
bool IsEmpty() { return total_count_ == 0; }
// TODO: Unify code between DequeuePlusAction with TryDequeuePlusAction.
// Probably have opt<T> Dequeue(bool wait_for_element);
// Get the first element from the queue. Blocks until one is available.
2017-07-29 00:07:27 +00:00
// Executes |action| with an acquired |mutex_|.
2017-09-22 01:14:57 +00:00
template <typename TAction>
2017-07-30 04:46:21 +00:00
T DequeuePlusAction(TAction action) {
2017-03-25 19:18:25 +00:00
std::unique_lock<std::mutex> lock(mutex_);
2017-09-22 01:14:57 +00:00
waiter_->cv.wait(lock,
[&]() { return !priority_.empty() || !queue_.empty(); });
2017-07-29 00:07:27 +00:00
2017-10-31 19:37:52 +00:00
auto execute = [&](std::queue<T>* q) {
auto val = std::move(q->front());
q->pop();
--total_count_;
2017-07-29 00:07:27 +00:00
2017-10-31 19:37:52 +00:00
action();
2017-07-29 00:07:27 +00:00
2017-10-31 19:37:52 +00:00
return std::move(val);
};
if (!priority_.empty())
return execute(&priority_);
return execute(&queue_);
2017-03-25 19:18:25 +00:00
}
2017-07-29 00:07:27 +00:00
// Get the first element from the queue. Blocks until one is available.
T Dequeue() {
return DequeuePlusAction([]() {});
}
2017-03-25 20:32:44 +00:00
// Get the first element from the queue without blocking. Returns a null
// value if the queue is empty.
2017-09-22 01:14:57 +00:00
template <typename TAction>
2017-09-13 03:35:27 +00:00
optional<T> TryDequeuePlusAction(TAction action) {
std::lock_guard<std::mutex> lock(mutex_);
if (priority_.empty() && queue_.empty())
2017-03-25 19:18:25 +00:00
return nullopt;
2017-10-31 19:37:52 +00:00
auto execute = [&](std::queue<T>* q) {
auto val = std::move(q->front());
q->pop();
--total_count_;
2017-10-31 19:37:52 +00:00
action(val);
2017-09-13 03:35:27 +00:00
2017-10-31 19:37:52 +00:00
return std::move(val);
};
if (!priority_.empty())
return execute(&priority_);
return execute(&queue_);
2017-03-25 19:18:25 +00:00
}
2017-09-13 03:35:27 +00:00
optional<T> TryDequeue() {
return TryDequeuePlusAction([](const T&) {});
}
2017-03-25 20:32:44 +00:00
private:
2017-10-31 19:37:52 +00:00
std::atomic<int> total_count_;
std::queue<T> priority_;
2017-03-25 19:18:25 +00:00
mutable std::mutex mutex_;
std::queue<T> queue_;
MultiQueueWaiter* waiter_;
std::unique_ptr<MultiQueueWaiter> owned_waiter_;
2017-03-25 19:18:25 +00:00
};