/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Copyright 2017 Danny Robson */ #ifndef CRUFT_UTIL_JOB_QUEUE_HPP #define CRUFT_UTIL_JOB_QUEUE_HPP #include "../pool.hpp" #include "../tuple.hpp" #include "ticketlock.hpp" #include "semaphore.hpp" #include "flag.hpp" #include "monitor.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace util::job { class queue { public: queue (); explicit queue (unsigned thread_count); ~queue (); queue (const queue&) = delete; queue (queue&&) = delete; queue& operator= (const queue&) = delete; queue& operator= (queue&&) = delete; auto parallelism (void) const { return m_threads.size (); } struct task; struct [[nodiscard]] cookie { ~cookie () { if (data) { data->done.wait (); data->references.release (); } } task& operator-> (void) { return *data; } cookie (task &_data, queue &_runner): data (&_data), runner (_runner) { ; } cookie (cookie &&rhs): data (nullptr), runner (rhs.runner) { std::swap (data, rhs.data); } cookie& operator= (cookie&&) = delete; cookie (const cookie&) = delete; cookie& operator= (const cookie&) = delete; task *data; queue &runner; }; template cookie submit (task &parent, Function&&, Args&&...); /// record a functor and a set of parameters to execute at some point /// in the future by an arbitrary available thread. template cookie submit (Function &&func, Args &&...args) { CHECK (!m_stopping); auto ptr = m_tasks.store.construct ( std::forward (func), std::forward (args)... ); m_tasks.pending->push_back (ptr); m_pending.release (); return cookie (*ptr, *this); } // block until all jobs currently queued have been started void flush (void); // block until there are no more jobs queued or executing void finish (void); /// stores a functor and associated arguments in a fixed size buffer /// for later execution. /// /// for ease of implementation the arguments are currently restricted /// as follows: /// * trivial types (memcpy'able) /// * a fixed total maximum size of around one cache line /// these limitations will be eliminated at some point in the future /// /// the user supplied functor is wrapped with our own that unpacks and /// forwards the arguments from the data buffer. this function must /// be passed a copy of the current arg object as the only argument. struct task { task () = default; ~task () { done.notify (); references.acquire (); } task (const task&) = delete; task (task&&) = delete; task& operator= (const task&) = delete; task& operator= (task&&) = delete; template task (FunctionT &&func, Args&&...params) { using tuple_t = std::tuple...>; static_assert (( ( std::is_trivially_copyable_v || std::is_scalar_v> || is_same_template_template_v ) && ...) ); static_assert (sizeof (tuple_t) <= sizeof data); tuple_t &punned = *reinterpret_cast (&data); punned = tuple_t (params...); if constexpr (std::is_function_v>) { function = [f=std::ref(func)] (task &base) { std::apply (f, *reinterpret_cast (&base.data)); }; } else { function = [func] (task &base) { std::apply (func, *reinterpret_cast (&base.data)); }; } } void acquire (void) { --references; } void release (void) { ++references; } // GCC: switch to hardware_destructive_interference_size when it // becomes available in libstdc++. Until then we use a sensible // guess. std::array data; std::function function; semaphore references = 0; flag done; }; private: void loop (); std::atomic m_stopping = false; std::atomic m_running = 0; struct { monitor< std::deque, ticketlock > pending; pool store; } m_tasks; semaphore m_pending; std::vector m_threads; }; } #endif