1. 程式人生 > >folly無鎖隊列正確性說明

folly無鎖隊列正確性說明

bec lB when date 比較 similar source lis 通過

folly無鎖隊列是facebook開源的一個無所隊列,使用的是單向鏈表,通過compare_exchange語句實現的多生產多消費的隊列,我曾經花了比較多的時間學習memory_order的說明,對release-acquire語義,自認為還是比較了解。如果一個atomic對象使用std::memory_order_release進行寫操作,而另外一個線程使用std::memory_order_acquire進行讀操作,那麽這兩個線程之間形成同步關系。std::memory_order_release之前寫的效果,在std::memory_order_acquire之後可見。不過對於多生產多消費模型,存在多個生產者的情況,在有多個生產者的情況下,結果正確嗎?

這裏給出folly的源代碼,這裏請重點關註insertHead函數和sweepOnce函數。

/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
* * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ #pragma once #include <atomic> #include <cassert> #include <utility> namespace folly { /** * A very simple atomic single-linked list primitive. * * Usage: * * class MyClass { * AtomicIntrusiveLinkedListHook<MyClass> hook_; * } * * AtomicIntrusiveLinkedList<MyClass, &MyClass::hook_> list; * list.insert(&a); * list.sweep([] (MyClass* c) { doSomething(c); } */ template <class T> struct AtomicIntrusiveLinkedListHook { T* next{ nullptr }; }; template <class T, AtomicIntrusiveLinkedListHook<T> T::*HookMember> class AtomicIntrusiveLinkedList { public: AtomicIntrusiveLinkedList() {} AtomicIntrusiveLinkedList(const AtomicIntrusiveLinkedList&) = delete; AtomicIntrusiveLinkedList& operator=(const AtomicIntrusiveLinkedList&) = delete; AtomicIntrusiveLinkedList(AtomicIntrusiveLinkedList&& other) noexcept { auto tmp = other.head_.load(); other.head_ = head_.load(); head_ = tmp; } AtomicIntrusiveLinkedList& operator=( AtomicIntrusiveLinkedList&& other) noexcept { auto tmp = other.head_.load(); other.head_ = head_.load(); head_ = tmp; return *this; } /** * Note: list must be empty on destruction. */ ~AtomicIntrusiveLinkedList() { assert(empty()); } bool empty() const { return head_.load() == nullptr; } /** * Atomically insert t at the head of the list. * @return True if the inserted element is the only one in the list * after the call. */ bool insertHead(T* t) { assert(next(t) == nullptr); auto oldHead = head_.load(std::memory_order_relaxed); do { next(t) = oldHead; /* oldHead is updated by the call below. NOTE: we don‘t use next(t) instead of oldHead directly due to compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899), MSVC (bug 819819); source: http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */ } while (!head_.compare_exchange_weak(oldHead, t, std::memory_order_release, std::memory_order_relaxed)); return oldHead == nullptr; } /** * Replaces the head with nullptr, * and calls func() on the removed elements in the order from tail to head. * Returns false if the list was empty. */ template <typename F> bool sweepOnce(F&& func) { if (auto head = head_.exchange(nullptr)) { auto rhead = reverse(head); unlinkAll(rhead, std::forward<F>(func)); return true; } return false; }/** * Repeatedly replaces the head with nullptr, * and calls func() on the removed elements in the order from tail to head. * Stops when the list is empty. */ template <typename F> void sweep(F&& func) { while (sweepOnce(func)) { } } /** * Similar to sweep() but calls func() on elements in LIFO order. * * func() is called for all elements in the list at the moment * reverseSweep() is called. Unlike sweep() it does not loop to ensure the * list is empty at some point after the last invocation. This way callers * can reason about the ordering: elements inserted since the last call to * reverseSweep() will be provided in LIFO order. * * Example: if elements are inserted in the order 1-2-3, the callback is * invoked 3-2-1. If the callback moves elements onto a stack, popping off * the stack will produce the original insertion order 1-2-3. */ template <typename F> void reverseSweep(F&& func) { // We don‘t loop like sweep() does because the overall order of callbacks // would be strand-wise LIFO which is meaningless to callers. auto head = head_.exchange(nullptr); unlinkAll(head, std::forward<F>(func)); } private: std::atomic<T*> head_{ nullptr }; static T*& next(T* t) { return (t->*HookMember).next; } /* Reverses a linked list, returning the pointer to the new head (old tail) */ static T* reverse(T* head) { T* rhead = nullptr; while (head != nullptr) { auto t = head; head = next(t); next(t) = rhead; rhead = t; } return rhead; } /* Unlinks all elements in the linked list fragment pointed to by `head‘, * calling func() on every element */ template <typename F> void unlinkAll(T* head, F&& func) { while (head != nullptr) { auto t = head; head = next(t); next(t) = nullptr; func(t); } } }; } // namespace folly

如果存在兩個線程先後向同一個隊列中插入節點,由於兩個線程中沒有一個使用acquire,如果僅按照release-acquire語義,顯然,正確性無法保證,後一個insertHead函數中,無論是auto oldHead = head_.load(std::memory_order_relaxed);,還是while (!head_.compare_exchange_weak(oldHead, t, std::memory_order_release,std::memory_order_relaxed));都可能讀取的是前一個線程插入前的數據。那麽,還有什麽C++語義,可以保證folly隊列的正確性?那就是release sequence。release sequence其中的一部分說的是:

如果一個存儲使用memory_order_release或更嚴格的內存序,後面跟著若幹讀-改-寫(read-modify-write)(可以是同一個線程,也可以是不同的線程)操作的話。

(1)那麽中間的讀-改-寫操作 讀取的要麽是前一次讀-改-寫的結果,要麽是存儲的數據。

那麽,如果存在一個release操作,後面跟著一個讀改寫操作的話,這個讀改寫操作肯定會得到之前release操作寫入的效果。我們可以觀察到insertHead中的compare_exchange_weak為一個release操作,同時也是一個讀改寫操作,那麽前面一個線程的修改,一定會在後面一個compare_exchange_weak中可見,無論是同一個線程調用,還是不同線程調用。註意到auto oldHead = head_.load(std::memory_order_relaxed);得到的結果的正確性與否,不影響compare_exchange_weak的正確性,因為如果前一個讀取的結果是舊值,這個操作就會失敗,而且將oldHead的值更新為最新值,這點對於理解folly的正確性很重要。其他的情況應該根據類似的原理得到正確的解答,這裏就不詳細說明了。

folly無鎖隊列正確性說明