[英]c++ how to make lock free stack push atomic
我需要为无锁堆栈编写一个void push(const T& val)
实现。 问题是compare_exchange_weak期望使用非原子node*
但是我必须使用std::atomic<node*> next
字段而不是常规node* next
。 我试图通过这样做来解决这个问题
void push(const T& val) {
node* new_node = new node(val);
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
但是创建if local_next
会使情况变得更糟。 我测试了2种代码变体。 第一个node* next
是非原子场node* next
,在下面的测试代码中我丢失了大约20-30个元素。 使用第二个变体,我陷入了僵局。
测试代码:
#include <iostream>
#include <thread>
#include <atomic>
#include "lock_free_stack.h"
using namespace std;
void test(lock_free_stack<int>& st, atomic<int>& sum) {
st.push(1);
shared_ptr<int> val(st.pop());
while (val == nullptr) { }
sum.store(sum.load() + *val);
}
int main(int argc, const char * argv[]) {
atomic<int> sum;
sum.store(0);
for (int i = 0; i < 100; ++i) {
lock_free_stack<int> st;
thread t1(test, ref(st), ref(sum));
thread t2(test, ref(st), ref(sum));
thread t3(test, ref(st), ref(sum));
thread t4(test, ref(st), ref(sum));
thread t5(test, ref(st), ref(sum));
thread t6(test, ref(st), ref(sum));
thread t7(test, ref(st), ref(sum));
thread t8(test, ref(st), ref(sum));
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
t6.join();
t7.join();
t8.join();
}
if (sum.load() == 800) {
cout << "CORRECT" << endl;
} else {
cout << "TIME TO REWRITE STACK " << sum << endl;
}
return 0;
}
还有我的无锁堆栈的代码(第一个变体):
#ifndef lock_free_stack_hard_lock_free_stack_h
#define lock_free_stack_hard_lock_free_stack_h
template <typename T>
class lock_free_stack {
private:
struct node {
node* next;
std::shared_ptr<T> value;
node (const T& val) : value(std::make_shared<T>(val)) { }
};
std::atomic<node*> head;
std::shared_ptr<T> default_value;
public:
lock_free_stack() : head(nullptr), default_value(std::make_shared<T>()) { }
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next, new_node));
}
std::shared_ptr<T> pop() {
node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next));
if (old_head) {
return old_head->value;
} else {
return std::shared_ptr<T>();
}
}
};
#endif
第二个变种:
#ifndef lock_free_stack_hard_lock_free_stack_h
#define lock_free_stack_hard_lock_free_stack_h
template <typename T>
class lock_free_stack {
private:
struct node {
std::atomic<node*> next;
std::shared_ptr<T> value;
node (const T& val) : value(std::make_shared<T>(val)) { }
};
std::atomic<node*> head;
std::shared_ptr<T> default_value;
public:
lock_free_stack() : head(nullptr), default_value(std::make_shared<T>()) { }
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
std::shared_ptr<T> pop() {
node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next));
if (old_head) {
return old_head->value;
} else {
return std::shared_ptr<T>();
}
}
};
#endif
因此,最后一个问题是如何正确创建该local_next
?
谢谢。
测试的一个问题是行sum.store(sum.load() + *val);
使用原子操作,例如sum += *val;
第一种是垃圾,因为您不能保证在存储到node :: next指针的原子性。 可以将内存屏障/屏障与非原子的下一个指针一起使用,但是我不相信这种实现。 您的第二个变体更接近正确的实现。 但是,您忘记了push()CAS循环中最重要的事情:
void push(const T& val) {
node* new_node = new node(val);
new_node->next = head.load();
node* local_next = new_node->next.load();
while (!head.compare_exchange_weak(local_next, new_node));
}
这里的代码分配新节点,加载并存储指向new_node->next
head
指针。 接下来,代码将已知的堆栈head
指针值保存到local_next
。 (不必要的步骤)然后代码尝试将堆栈head
更新为new_node
而不更新new_node->next
。 如果您在运行单线程且没有抢占的单线程核心计算机上运行,这会很好,并且CAS将成功100%的时间。 ;)
当CAS失败时,它会将head
当前新值加载到local_next
,并且代码陷入无限循环,因为new_node
永远不会等于local_next
。 所以你把最后一部分弄错了。
为了使CAS循环正常运行,失败的线程必须重新加载并重新计算它试图更新的数据。 这意味着你必须更新new_node->next
从head
重新尝试CAS之前。 这不能解决CAS循环的ABA问题,但我不在回答之列。 我建议阅读更多有关CAS操作及其陷阱的信息。
由于CAS操作做了负载运行的解决方法是很简单,只是存储local_next
到new_node->next
失败的CAS后。 这是更有效(未试用)的版本:
node* new_node = new node(val);
node* local_head = head.load();
new_node->next.store(local_head);
while(!head.compare_exchange_weak(local_head, new_node) {
new_node->next.store(local_head);
}
您将对pop()实现做类似的事情。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.