• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
Entry(T * value)44         Entry(T* value): mValue(value) {}
45     };
46     std::atomic<Entry*> mPush = nullptr;
47     std::atomic<Entry*> mPop = nullptr;
push(T * value)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     }
pop()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 };
73