File indexing completed on 2024-05-12 15:56:58
0001 /* 0002 * SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * References: 0005 * * Maged M. Michael, Safe memory reclamation for dynamic 0006 * lock-free objects using atomic reads and writes, 0007 * Proceedings of the twenty-first annual symposium on 0008 * Principles of distributed computing, July 21-24, 2002, 0009 * Monterey, California 0010 * 0011 * * Idea of m_deleteBlockers is taken from Andrey Gulin's blog 0012 * https://users.livejournal.com/-foreseer/34284.html 0013 * 0014 * SPDX-License-Identifier: GPL-2.0-or-later 0015 */ 0016 0017 #ifndef __KIS_LOCKLESS_STACK_H 0018 #define __KIS_LOCKLESS_STACK_H 0019 0020 #include <QAtomicPointer> 0021 0022 template<class T> 0023 class KisLocklessStack 0024 { 0025 private: 0026 struct Node { 0027 Node *next; 0028 T data; 0029 }; 0030 0031 public: 0032 KisLocklessStack() { } 0033 ~KisLocklessStack() { 0034 0035 freeList(m_top.fetchAndStoreOrdered(0)); 0036 freeList(m_freeNodes.fetchAndStoreOrdered(0)); 0037 } 0038 0039 void push(T data) { 0040 Node *newNode = new Node(); 0041 newNode->data = data; 0042 0043 Node *top; 0044 0045 do { 0046 top = m_top; 0047 newNode->next = top; 0048 } while (!m_top.testAndSetOrdered(top, newNode)); 0049 0050 m_numNodes.ref(); 0051 } 0052 0053 bool pop(T &value) { 0054 bool result = false; 0055 0056 m_deleteBlockers.ref(); 0057 0058 while(1) { 0059 Node *top = (Node*) m_top; 0060 if(!top) break; 0061 0062 // This is safe as we ref'ed m_deleteBlockers 0063 Node *next = top->next; 0064 0065 if(m_top.testAndSetOrdered(top, next)) { 0066 m_numNodes.deref(); 0067 result = true; 0068 0069 value = top->data; 0070 0071 /** 0072 * Test if we are the only delete blocker left 0073 * (it means that we are the only owner of 'top') 0074 * If there is someone else in "delete-blocked section", 0075 * then just add the struct to the list of free nodes. 0076 */ 0077 if (m_deleteBlockers == 1) { 0078 cleanUpNodes(); 0079 delete top; 0080 } 0081 else { 0082 releaseNode(top); 0083 } 0084 0085 break; 0086 } 0087 } 0088 0089 m_deleteBlockers.deref(); 0090 0091 return result; 0092 } 0093 0094 void clear() { 0095 // a fast-path without write ops 0096 if(!m_top) return; 0097 0098 m_deleteBlockers.ref(); 0099 0100 Node *top = m_top.fetchAndStoreOrdered(0); 0101 0102 int removedChunkSize = 0; 0103 Node *tmp = top; 0104 while(tmp) { 0105 removedChunkSize++; 0106 tmp = tmp->next; 0107 } 0108 m_numNodes.fetchAndAddOrdered(-removedChunkSize); 0109 0110 while(top) { 0111 Node *next = top->next; 0112 0113 if (m_deleteBlockers == 1) { 0114 /** 0115 * We are the only owner of top contents. 0116 * So we can delete it freely. 0117 */ 0118 cleanUpNodes(); 0119 freeList(top); 0120 next = 0; 0121 } 0122 else { 0123 releaseNode(top); 0124 } 0125 0126 top = next; 0127 } 0128 0129 m_deleteBlockers.deref(); 0130 } 0131 0132 void mergeFrom(KisLocklessStack<T> &other) { 0133 Node *otherTop = other.m_top.fetchAndStoreOrdered(0); 0134 if (!otherTop) return; 0135 0136 int removedChunkSize = 1; 0137 Node *last = otherTop; 0138 while(last->next) { 0139 removedChunkSize++; 0140 last = last->next; 0141 } 0142 other.m_numNodes.fetchAndAddOrdered(-removedChunkSize); 0143 0144 Node *top; 0145 0146 do { 0147 top = m_top; 0148 last->next = top; 0149 } while (!m_top.testAndSetOrdered(top, otherTop)); 0150 0151 m_numNodes.fetchAndAddOrdered(removedChunkSize); 0152 } 0153 0154 /** 0155 * This is impossible to measure the size of the stack 0156 * in highly concurrent environment. So we return approximate 0157 * value! Do not rely on this value much! 0158 */ 0159 qint32 size() const { 0160 return m_numNodes; 0161 } 0162 0163 bool isEmpty() const { 0164 return !m_numNodes; 0165 } 0166 0167 private: 0168 0169 inline void releaseNode(Node *node) { 0170 Node *top; 0171 do { 0172 top = m_freeNodes; 0173 node->next = top; 0174 } while (!m_freeNodes.testAndSetOrdered(top, node)); 0175 } 0176 0177 inline void cleanUpNodes() { 0178 Node *cleanChain = m_freeNodes.fetchAndStoreOrdered(0); 0179 if (!cleanChain) return; 0180 0181 /** 0182 * If we are the only users of the objects is cleanChain, 0183 * then just free it. Otherwise, push them back into the 0184 * recycling list and keep them there till another 0185 * chance comes. 0186 */ 0187 if (m_deleteBlockers == 1) { 0188 freeList(cleanChain); 0189 } else { 0190 Node *last = cleanChain; 0191 while (last->next) last = last->next; 0192 0193 Node *freeTop; 0194 0195 do { 0196 freeTop = m_freeNodes; 0197 last->next = freeTop; 0198 } while (!m_freeNodes.testAndSetOrdered(freeTop, cleanChain)); 0199 } 0200 } 0201 0202 inline void freeList(Node *first) { 0203 Node *next; 0204 while (first) { 0205 next = first->next; 0206 delete first; 0207 first = next; 0208 } 0209 } 0210 0211 0212 private: 0213 Q_DISABLE_COPY(KisLocklessStack) 0214 0215 QAtomicPointer<Node> m_top; 0216 QAtomicPointer<Node> m_freeNodes; 0217 0218 QAtomicInt m_deleteBlockers; 0219 QAtomicInt m_numNodes; 0220 }; 0221 0222 #endif /* __KIS_LOCKLESS_STACK_H */ 0223