[英]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.