ich eine Lock-Frei-Warteschlange implementiert zu implementieren, basierend auf dem Algorithmus angegeben in Maged M. Michael und Michael L. Scott Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms Arbeit (für den Algorithmus, um springen Seite 4)Stack-Überlauf, wenn sie versuchen Lock-freie Warteschlange
I verwendet atomare Operation auf shared_ptr
wie std::atomic_load_explicit
usw.
wenn nur die Warteschlange in einem Thread verwendet wird, alles in Ordnung ist, aber wenn es von anderen Thread verwendet wird, I eine Stapelüberlaufausnahme erhalten.
Ich konnte die Quelle des Problems leider nicht verfolgen. es scheint, dass, wenn man shared_ptr
ist out of scope bekommen, es die Anzahl der Referenzen auf den nächsten ConcurrentQueueNode
dekrementiert und es eine unendliche Rekursion verursacht, aber ich kann nicht sehen, warum ..
den Code:
die Warteschlangen-Knoten:
template<class T>
struct ConcurrentQueueNode {
T m_Data;
std::shared_ptr<ConcurrentQueueNode> m_Next;
template<class ... Args>
ConcurrentQueueNode(Args&& ... args) :
m_Data(std::forward<Args>(args)...) {}
std::shared_ptr<ConcurrentQueueNode>& getNext() {
return m_Next;
}
T getValue() {
return std::move(m_Data);
}
};
die gleichzeitige Warteschlange (Anmerkung: nicht für schwache Nerven):
template<class T>
class ConcurrentQueue {
std::shared_ptr<ConcurrentQueueNode<T>> m_Head, m_Tail;
public:
ConcurrentQueue(){
m_Head = m_Tail = std::make_shared<ConcurrentQueueNode<T>>();
}
template<class ... Args>
void push(Args&& ... args) {
auto node = std::make_shared<ConcurrentQueueNode<T>>(std::forward<Args>(args)...);
std::shared_ptr<ConcurrentQueueNode<T>> tail;
for (;;) {
tail = std::atomic_load_explicit(&m_Tail, std::memory_order_acquire);
std::shared_ptr<ConcurrentQueueNode<T>> next =
std::atomic_load_explicit(&tail->getNext(),std::memory_order_acquire);
if (tail == std::atomic_load_explicit(&m_Tail, std::memory_order_acquire)) {
if (next.get() == nullptr) {
auto currentNext = std::atomic_load_explicit(&m_Tail, std::memory_order_acquire)->getNext();
auto res = std::atomic_compare_exchange_weak(&tail->getNext(), &next, node);
if (res) {
break;
}
}
else {
std::atomic_compare_exchange_weak(&m_Tail, &tail, next);
}
}
}
std::atomic_compare_exchange_strong(&m_Tail, &tail, node);
}
bool tryPop(T& dest) {
std::shared_ptr<ConcurrentQueueNode<T>> head;
for (;;) {
head = std::atomic_load_explicit(&m_Head, std::memory_order_acquire);
auto tail = std::atomic_load_explicit(&m_Tail,std::memory_order_acquire);
auto next = std::atomic_load_explicit(&head->getNext(), std::memory_order_acquire);
if (head == std::atomic_load_explicit(&m_Head, std::memory_order_acquire)) {
if (head.get() == tail.get()) {
if (next.get() == nullptr) {
return false;
}
std::atomic_compare_exchange_weak(&m_Tail, &tail, next);
}
else {
dest = next->getValue();
auto res = std::atomic_compare_exchange_weak(&m_Head, &head, next);
if (res) {
break;
}
}
}
}
return true;
}
};
Beispiel Verwendung, die repro duces das Problem:
int main(){
ConcurrentQueue<int> queue;
std::thread threads[4];
for (auto& thread : threads) {
thread = std::thread([&queue] {
for (auto i = 0; i < 100'000; i++) {
queue.push(i);
int y;
queue.tryPop(y);
}
});
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
Ich denke, ich schätze mich glücklich, dass meine Nebenläufigkeitsbedürfnisse immer von Garten-Diversity-Mutexe erfüllt werden, mit wohldefinierter Semantik, und dass ich mich nicht dazu bringen muss, riesige Mengen an Zeit auf wackeligen, verschlungenen zu versenken. " Lock-free "Alternativen, die merkwürdigerweise immer Looping-Spinlocks implementieren, die die Ausführung effektiv blockieren, genau wie ein gewöhnlicher Mutex. –
@SamVarshavchik Ich denke du hast recht, aber es ist immer gut experimentieren –
Wenn Sie etwas, das frei von Sperren ist, implementieren, schlage ich vor, dass Ihre Standardimplementierung für einen Aufruf von 'std :: atomic_is_lock_free (& ptr)' 'wahr 'zurückgibt where 'ptr' ist eine' std :: shared_ptr <> 'Instanz. –