Skip to content

Commit a951520

Browse files
committed
tmp
1 parent d19e2da commit a951520

File tree

2 files changed

+321
-62
lines changed

2 files changed

+321
-62
lines changed
Lines changed: 287 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ namespace std {
216216
* See the License for the specific language governing permissions and
217217
* limitations under the License.
218218
*/
219-
namespace atomic {
219+
namespace lock_free {
220220

221221
size_t nextPowTwo(size_t v) {
222222
#ifdef _MSC_VER
@@ -593,4 +593,289 @@ namespace atomic {
593593
explicit MutableData(const T& init) : data(init) {}
594594
};
595595

596-
} // namespace atomic
596+
/**
597+
* A very simple atomic single-linked list primitive.
598+
*
599+
* Usage:
600+
*
601+
* class MyClass {
602+
* _linked_list_hook<MyClass> hook_;
603+
* }
604+
*
605+
* _linked_list<MyClass, &MyClass::hook_> list;
606+
* list.insert(&a);
607+
* list.sweep([] (MyClass* c) { doSomething(c); }
608+
*/
609+
template <class T>
610+
struct _linked_list_hook {
611+
T* next{nullptr};
612+
};
613+
614+
template <class T, _linked_list_hook<T> T::*HookMember>
615+
class _linked_list {
616+
public:
617+
_linked_list() {}
618+
619+
_linked_list(const _linked_list&) = delete;
620+
_linked_list& operator=(const _linked_list&) =
621+
delete;
622+
623+
_linked_list(_linked_list&& other) noexcept
624+
: head_(other.head_.exchange(nullptr, std::memory_order_acq_rel)) {}
625+
626+
// Absent because would be too error-prone to use correctly because of
627+
// the requirement that lists are empty upon destruction.
628+
_linked_list& operator=(
629+
_linked_list&& other) noexcept = delete;
630+
631+
/**
632+
* Move the currently held elements to a new list.
633+
* The current list becomes empty, but concurrent threads
634+
* might still add new elements to it.
635+
*
636+
* Equivalent to calling a move constructor, but more linter-friendly
637+
* in case you still need the old list.
638+
*/
639+
_linked_list spliceAll() { return std::move(*this); }
640+
641+
/**
642+
* Move-assign the current list to `other`, then reverse-sweep
643+
* the old list with the provided callback `func`.
644+
*
645+
* A safe replacement for the move assignment operator, which is absent
646+
* because of the resource leak concerns.
647+
*/
648+
template <typename F>
649+
void reverseSweepAndAssign(_linked_list&& other, F&& func) {
650+
auto otherHead = other.head_.exchange(nullptr, std::memory_order_acq_rel);
651+
auto head = head_.exchange(otherHead, std::memory_order_acq_rel);
652+
unlinkAll(head, std::forward<F>(func));
653+
}
654+
655+
/**
656+
* Note: The list must be empty on destruction.
657+
*/
658+
~_linked_list() { assert(empty()); }
659+
660+
/**
661+
* Returns the current head of the list.
662+
*
663+
* WARNING: The returned pointer might not be valid if the list
664+
* is modified concurrently!
665+
*/
666+
T* unsafeHead() const { return head_.load(std::memory_order_acquire); }
667+
668+
/**
669+
* Returns true if the list is empty.
670+
*
671+
* WARNING: This method's return value is only valid for a snapshot
672+
* of the state, it might become stale as soon as it's returned.
673+
*/
674+
bool empty() const { return unsafeHead() == nullptr; }
675+
676+
/**
677+
* Atomically insert t at the head of the list.
678+
* @return True if the inserted element is the only one in the list
679+
* after the call.
680+
*/
681+
bool insertHead(T* t) {
682+
assert(next(t) == nullptr);
683+
684+
auto oldHead = head_.load(std::memory_order_relaxed);
685+
do {
686+
next(t) = oldHead;
687+
/* oldHead is updated by the call below.
688+
689+
NOTE: we don't use next(t) instead of oldHead directly due to
690+
compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899),
691+
MSVC (bug 819819); source:
692+
http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */
693+
} while (!head_.compare_exchange_weak(
694+
oldHead, t, std::memory_order_release, std::memory_order_relaxed));
695+
696+
return oldHead == nullptr;
697+
}
698+
699+
/**
700+
* Replaces the head with nullptr,
701+
* and calls func() on the removed elements in the order from tail to head.
702+
* Returns false if the list was empty.
703+
*/
704+
template <typename F>
705+
bool sweepOnce(F&& func) {
706+
if (auto head = head_.exchange(nullptr, std::memory_order_acq_rel)) {
707+
auto rhead = reverse(head);
708+
unlinkAll(rhead, std::forward<F>(func));
709+
return true;
710+
}
711+
return false;
712+
}
713+
714+
/**
715+
* Repeatedly replaces the head with nullptr,
716+
* and calls func() on the removed elements in the order from tail to head.
717+
* Stops when the list is empty.
718+
*/
719+
template <typename F>
720+
void sweep(F&& func) {
721+
while (sweepOnce(func)) {
722+
}
723+
}
724+
725+
/**
726+
* Similar to sweep() but calls func() on elements in LIFO order.
727+
*
728+
* func() is called for all elements in the list at the moment
729+
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
730+
* list is empty at some point after the last invocation. This way callers
731+
* can reason about the ordering: elements inserted since the last call to
732+
* reverseSweep() will be provided in LIFO order.
733+
*
734+
* Example: if elements are inserted in the order 1-2-3, the callback is
735+
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
736+
* the stack will produce the original insertion order 1-2-3.
737+
*/
738+
template <typename F>
739+
void reverseSweep(F&& func) {
740+
// We don't loop like sweep() does because the overall order of callbacks
741+
// would be strand-wise LIFO which is meaningless to callers.
742+
auto head = head_.exchange(nullptr, std::memory_order_acq_rel);
743+
unlinkAll(head, std::forward<F>(func));
744+
}
745+
746+
private:
747+
std::atomic<T*> head_{nullptr};
748+
749+
static T*& next(T* t) { return (t->*HookMember).next; }
750+
751+
/* Reverses a linked list, returning the pointer to the new head
752+
(old tail) */
753+
static T* reverse(T* head) {
754+
T* rhead = nullptr;
755+
while (head != nullptr) {
756+
auto t = head;
757+
head = next(t);
758+
next(t) = rhead;
759+
rhead = t;
760+
}
761+
return rhead;
762+
}
763+
764+
/* Unlinks all elements in the linked list fragment pointed to by `head',
765+
* calling func() on every element */
766+
template <typename F>
767+
static void unlinkAll(T* head, F&& func) {
768+
while (head != nullptr) {
769+
auto t = head;
770+
head = next(t);
771+
next(t) = nullptr;
772+
func(t);
773+
}
774+
}
775+
};
776+
777+
/**
778+
* A very simple atomic single-linked list primitive.
779+
*
780+
* Usage:
781+
*
782+
* linked_list<MyClass> list;
783+
* list.insert(a);
784+
* list.sweep([] (MyClass& c) { doSomething(c); }
785+
*/
786+
787+
template <class T>
788+
class linked_list {
789+
public:
790+
linked_list() {}
791+
linked_list(const linked_list&) = delete;
792+
linked_list& operator=(const linked_list&) = delete;
793+
linked_list(linked_list&& other) noexcept = default;
794+
linked_list& operator=(linked_list&& other) noexcept {
795+
list_.reverseSweepAndAssign(std::move(other.list_), [](Wrapper* node) {
796+
delete node;
797+
});
798+
return *this;
799+
}
800+
801+
~linked_list() {
802+
sweep([](T&&) {});
803+
}
804+
805+
bool empty() const { return list_.empty(); }
806+
807+
/**
808+
* Atomically insert t at the head of the list.
809+
* @return True if the inserted element is the only one in the list
810+
* after the call.
811+
*/
812+
bool insertHead(T t) {
813+
auto wrapper = std::make_unique<Wrapper>(std::move(t));
814+
815+
return list_.insertHead(wrapper.release());
816+
}
817+
818+
/**
819+
* Repeatedly pops element from head,
820+
* and calls func() on the removed elements in the order from tail to head.
821+
* Stops when the list is empty.
822+
*/
823+
template <typename F>
824+
void sweep(F&& func) {
825+
list_.sweep([&](Wrapper* wrapperPtr) mutable {
826+
std::unique_ptr<Wrapper> wrapper(wrapperPtr);
827+
828+
func(std::move(wrapper->data));
829+
});
830+
}
831+
832+
/**
833+
* Sweeps the list a single time, as a single point in time swap with the
834+
* current contents of the list.
835+
*
836+
* Unlike sweep() it does not loop to ensure the list is empty at some point
837+
* after the last invocation.
838+
*
839+
* Returns false if the list is empty.
840+
*/
841+
template <typename F>
842+
bool sweepOnce(F&& func) {
843+
return list_.sweepOnce([&](Wrapper* wrappedPtr) {
844+
std::unique_ptr<Wrapper> wrapper(wrappedPtr);
845+
func(std::move(wrapper->data));
846+
});
847+
}
848+
849+
/**
850+
* Similar to sweep() but calls func() on elements in LIFO order.
851+
*
852+
* func() is called for all elements in the list at the moment
853+
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
854+
* list is empty at some point after the last invocation. This way callers
855+
* can reason about the ordering: elements inserted since the last call to
856+
* reverseSweep() will be provided in LIFO order.
857+
*
858+
* Example: if elements are inserted in the order 1-2-3, the callback is
859+
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
860+
* the stack will produce the original insertion order 1-2-3.
861+
*/
862+
template <typename F>
863+
void reverseSweep(F&& func) {
864+
list_.reverseSweep([&](Wrapper* wrapperPtr) mutable {
865+
std::unique_ptr<Wrapper> wrapper(wrapperPtr);
866+
867+
func(std::move(wrapper->data));
868+
});
869+
}
870+
871+
private:
872+
struct Wrapper {
873+
explicit Wrapper(T&& t) : data(std::move(t)) {}
874+
875+
_linked_list_hook<Wrapper> hook;
876+
T data;
877+
};
878+
_linked_list<Wrapper, &Wrapper::hook> list_;
879+
};
880+
881+
} // namespace lock_free

0 commit comments

Comments
 (0)