| Robert Carr | a63d52a | 2022-03-03 08:03:37 -0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright 2021 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 | 
|  | 18 | #include <atomic> | 
|  | 19 |  | 
|  | 20 | template <typename T> | 
|  | 21 | // Single consumer multi producer stack. We can understand the two operations independently to see | 
|  | 22 | // why they are without race condition. | 
|  | 23 | // | 
|  | 24 | // push is responsible for maintaining a linked list stored in mPush, and called from multiple | 
|  | 25 | // threads without lock. We can see that if two threads never observe the same value from | 
|  | 26 | // mPush.load, it just functions as a normal linked list. In the case where two threads observe the | 
|  | 27 | // same value, one of them has to execute the compare_exchange first. The one that doesn't execute | 
|  | 28 | // the compare exchange first, will receive false from compare_exchange. previousHead is updated (by | 
|  | 29 | // compare_exchange) to the most recent value of mPush, and we try again. It's relatively clear to | 
|  | 30 | // see that the process can repeat with an arbitrary number of threads. | 
|  | 31 | // | 
|  | 32 | // Pop is much simpler. If mPop is empty (as it begins) it atomically exchanges | 
|  | 33 | // the entire push list with null. This is safe, since the only other reader (push) | 
|  | 34 | // of mPush will retry if it changes in between it's read and atomic compare. We | 
|  | 35 | // then store the list and pop one element. | 
|  | 36 | // | 
|  | 37 | // If we already had something in the pop list we just pop directly. | 
|  | 38 | class LocklessStack { | 
|  | 39 | public: | 
|  | 40 | class Entry { | 
|  | 41 | public: | 
|  | 42 | T* mValue; | 
|  | 43 | std::atomic<Entry*> mNext; | 
|  | 44 | Entry(T* value): mValue(value) {} | 
|  | 45 | }; | 
|  | 46 | std::atomic<Entry*> mPush = nullptr; | 
|  | 47 | std::atomic<Entry*> mPop = nullptr; | 
|  | 48 | void push(T* value) { | 
|  | 49 | Entry* entry = new Entry(value); | 
|  | 50 | Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/); | 
|  | 51 | do { | 
|  | 52 | entry->mNext = previousHead; | 
|  | 53 | } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/ | 
|  | 54 | } | 
|  | 55 | T* pop() { | 
|  | 56 | Entry* popped = mPop.load(/*std::memory_order_acquire*/); | 
|  | 57 | if (popped) { | 
|  | 58 | // Single consumer so this is fine | 
|  | 59 | mPop.store(popped->mNext/* , std::memory_order_release */); | 
|  | 60 | auto value = popped->mValue; | 
|  | 61 | delete popped; | 
|  | 62 | return value; | 
|  | 63 | } else { | 
|  | 64 | Entry *grabbedList = mPush.exchange(nullptr/* , std::memory_order_acquire */); | 
|  | 65 | if (!grabbedList) return nullptr; | 
|  | 66 | mPop.store(grabbedList->mNext/* , std::memory_order_release */); | 
|  | 67 | auto value = grabbedList->mValue; | 
|  | 68 | delete grabbedList; | 
|  | 69 | return value; | 
|  | 70 | } | 
|  | 71 | } | 
|  | 72 | }; |