#include #include #include #include #include #include #include namespace mbgl { ThreadedSchedulerBase::~ThreadedSchedulerBase() = default; void ThreadedSchedulerBase::terminate() { { std::lock_guard lock(workerMutex); terminated = true; } // Wake up all threads so that they shut down cvAvailable.notify_all(); } std::thread ThreadedSchedulerBase::makeSchedulerThread(size_t index) { return std::thread([this, index] { auto& settings = platform::Settings::getInstance(); auto value = settings.get(platform::EXPERIMENTAL_THREAD_PRIORITY_WORKER); if (auto* priority = value.getDouble()) { platform::setCurrentThreadPriority(*priority); } platform::setCurrentThreadName("Worker " + util::toString(index + 1)); platform::attachThread(); owningThreadPool.set(this); while (true) { std::unique_lock conditionLock(workerMutex); if (!terminated && taskCount == 0) { cvAvailable.wait(conditionLock); } if (terminated) { platform::detachThread(); break; } // Let other threads run conditionLock.unlock(); std::vector> pending; { // 1. Gather buckets for us to visit this iteration std::lock_guard lock(taggedQueueLock); for (const auto& [tag, queue] : taggedQueue) { pending.push_back(queue); } } // 2. Visit a task from each for (auto& q : pending) { Task tasklet; { std::lock_guard lock(q->lock); if (q->queue.size()) { q->runningCount++; tasklet = std::move(q->queue.front()); q->queue.pop(); } if (!tasklet) continue; } assert(taskCount > 0); taskCount--; try { tasklet(); tasklet = {}; // destroy the function and release its captures before unblocking `waitForEmpty` if (!--q->runningCount) { std::lock_guard lock(q->lock); if (q->queue.empty()) { q->cv.notify_all(); } } } catch (...) { std::lock_guard lock(q->lock); if (handler) { handler(std::current_exception()); } tasklet = {}; if (!--q->runningCount && q->queue.empty()) { q->cv.notify_all(); } if (handler) { continue; } throw; } } } }); } void ThreadedSchedulerBase::schedule(Task&& fn) { schedule(uniqueID, std::move(fn)); } void ThreadedSchedulerBase::schedule(const util::SimpleIdentity tag, Task&& fn) { MLN_TRACE_FUNC(); assert(fn); if (!fn) return; std::shared_ptr q; { MLN_TRACE_ZONE(queue); std::lock_guard lock(taggedQueueLock); // find or insert auto result = taggedQueue.insert(std::make_pair(tag, std::shared_ptr{})); if (result.second) { // new entry inserted result.first->second = std::make_shared(); } q = result.first->second; MLN_ZONE_VALUE(taggedQueue.size()); } { MLN_TRACE_ZONE(push); std::lock_guard lock(q->lock); q->queue.push(std::move(fn)); taskCount++; } // Take the worker lock before notifying to prevent threads from waiting while we try to wake them std::lock_guard workerLock(workerMutex); cvAvailable.notify_one(); } void ThreadedSchedulerBase::waitForEmpty(const util::SimpleIdentity tag) { // Must not be called from a thread in our pool, or we would deadlock assert(!thisThreadIsOwned()); if (!thisThreadIsOwned()) { const auto tagToFind = tag.isEmpty() ? uniqueID : tag; std::shared_ptr q; { std::lock_guard lock(taggedQueueLock); auto it = taggedQueue.find(tagToFind); if (it == taggedQueue.end()) { return; } q = it->second; } std::unique_lock queueLock(q->lock); while (q->queue.size() + q->runningCount) { q->cv.wait(queueLock); } // After waiting for the queue to empty, go ahead and erase it from the map. { std::lock_guard lock(taggedQueueLock); taggedQueue.erase(tagToFind); } } } } // namespace mbgl