/* * Copyright 2018 The Android Open Source Project * * 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. */ #ifndef RHYTHMGAME_LOCKFREEQUEUE_H #define RHYTHMGAME_LOCKFREEQUEUE_H #include #include /** * A lock-free queue for single consumer, single producer. Not thread-safe when using multiple * consumers or producers. * * Example code: * * LockFreeQueue myQueue; * int value = 123; * myQueue.push(value); * myQueue.pop(value); * * @tparam T - The item type * @tparam CAPACITY - Maximum number of items which can be held in the queue. Must be a power of 2. * Must be less than the maximum value permissible in INDEX_TYPE * @tparam INDEX_TYPE - The internal index type, defaults to uint32_t. Changing this will affect * the maximum capacity. Included for ease of unit testing because testing queue lengths of * UINT32_MAX can be time consuming and is not always possible. */ template class LockFreeQueue { public: /** * Implementation details: * * We have 2 counters: readCounter and writeCounter. Each will increment until it reaches * INDEX_TYPE_MAX, then wrap to zero. Unsigned integer overflow is defined behaviour in C++. * * Each time we need to access our data array we call mask() which gives us the index into the * array. This approach avoids having a "dead item" in the buffer to distinguish between full * and empty states. It also allows us to have a size() method which is easily calculated. * * IMPORTANT: This implementation is only thread-safe with a single reader thread and a single * writer thread. Have more than one of either will result in Bad Thingsā„¢. */ static constexpr bool isPowerOfTwo(uint32_t n) { return (n & (n - 1)) == 0; } static_assert(isPowerOfTwo(CAPACITY), "Capacity must be a power of 2"); static_assert(std::is_unsigned::value, "Index type must be unsigned"); /** * Pop a value off the head of the queue * * @param val - element will be stored in this variable * @return true if value was popped successfully, false if the queue is empty */ bool pop(T &val) { if (isEmpty()){ return false; } else { val = buffer[mask(readCounter)]; ++readCounter; return true; } } /** * Add an item to the back of the queue * * @param item - The item to add * @return true if item was added, false if the queue was full */ bool push(const T& item) { if (isFull()){ return false; } else { buffer[mask(writeCounter)] = item; ++writeCounter; return true; } } /** * Get the item at the front of the queue but do not remove it * * @param item - item will be stored in this variable * @return true if item was stored, false if the queue was empty */ bool peek(T &item) const { if (isEmpty()){ return false; } else { item = buffer[mask(readCounter)]; return true; } } /** * Get the number of items in the queue * * @return number of items in the queue */ INDEX_TYPE size() const { /** * This is worth some explanation: * * Whilst writeCounter is greater than readCounter the result of (write - read) will always * be positive. Simple. * * But when writeCounter is equal to INDEX_TYPE_MAX (e.g. UINT32_MAX) the next push will * wrap it around to zero, the start of the buffer, making writeCounter less than * readCounter so the result of (write - read) will be negative. * * But because we're returning an unsigned type return value will be as follows: * * returnValue = INDEX_TYPE_MAX - (write - read) * * e.g. if write is 0, read is 150 and the INDEX_TYPE is uint8_t where the max value is * 255 the return value will be (255 - (0 - 150)) = 105. * */ return writeCounter - readCounter; }; private: bool isEmpty() const { return readCounter == writeCounter; } bool isFull() const { return size() == CAPACITY; } INDEX_TYPE mask(INDEX_TYPE n) const { return static_cast(n & (CAPACITY - 1)); } T buffer[CAPACITY]; std::atomic writeCounter { 0 }; std::atomic readCounter { 0 }; }; #endif //RHYTHMGAME_LOCKFREEQUEUE_H