Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2022 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 18 | |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 19 | #include <atomic> |
| 20 | #include <optional> |
| 21 | |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 22 | // Single consumer multi producer queue. We can understand the two operations independently to see |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 23 | // why they are without race condition. |
| 24 | // |
| 25 | // push is responsible for maintaining a linked list stored in mPush, and called from multiple |
| 26 | // threads without lock. We can see that if two threads never observe the same value from |
| 27 | // mPush.load, it just functions as a normal linked list. In the case where two threads observe the |
| 28 | // same value, one of them has to execute the compare_exchange first. The one that doesn't execute |
| 29 | // the compare exchange first, will receive false from compare_exchange. previousHead is updated (by |
| 30 | // compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to |
| 31 | // see that the process can repeat with an arbitrary number of threads. |
| 32 | // |
| 33 | // Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges |
| 34 | // the entire push list with null. This is safe, since the only other reader (push) |
| 35 | // of mPush will retry if it changes in between it's read and atomic compare. We |
| 36 | // then store the list and pop one element. |
| 37 | // |
| 38 | // If we already had something in the pop list we just pop directly. |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 39 | template <typename T> |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 40 | class LocklessQueue { |
| 41 | public: |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 42 | bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); } |
| 43 | |
| 44 | void push(T value) { |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 45 | Entry* entry = new Entry(std::move(value)); |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 46 | Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/); |
| 47 | do { |
| 48 | entry->mNext = previousHead; |
| 49 | } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/ |
| 50 | } |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 51 | |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 52 | std::optional<T> pop() { |
| 53 | Entry* popped = mPop.load(/*std::memory_order_acquire*/); |
| 54 | if (popped) { |
| 55 | // Single consumer so this is fine |
| 56 | mPop.store(popped->mNext /* , std::memory_order_release */); |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 57 | auto value = std::move(popped->mValue); |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 58 | delete popped; |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 59 | return value; |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 60 | } else { |
| 61 | Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */); |
| 62 | if (!grabbedList) return std::nullopt; |
| 63 | // Reverse the list |
| 64 | while (grabbedList->mNext) { |
| 65 | Entry* next = grabbedList->mNext; |
| 66 | grabbedList->mNext = popped; |
| 67 | popped = grabbedList; |
| 68 | grabbedList = next; |
| 69 | } |
| 70 | mPop.store(popped /* , std::memory_order_release */); |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 71 | auto value = std::move(grabbedList->mValue); |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 72 | delete grabbedList; |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 73 | return value; |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 74 | } |
| 75 | } |
Patrick Williams | 6de9f80 | 2024-07-15 16:43:02 -0500 | [diff] [blame] | 76 | |
| 77 | private: |
| 78 | class Entry { |
| 79 | public: |
| 80 | T mValue; |
| 81 | std::atomic<Entry*> mNext; |
| 82 | Entry(T value) : mValue(value) {} |
| 83 | }; |
| 84 | std::atomic<Entry*> mPush = nullptr; |
| 85 | std::atomic<Entry*> mPop = nullptr; |
Vishnu Nair | 60d902e | 2022-07-20 02:55:37 +0000 | [diff] [blame] | 86 | }; |