无锁队列以及ABA问题
隊列是我們非常常用的數據結構,用來提供數據的寫入和讀取功能,而且通常在不同線程之間作為數據通信的橋梁。不過在將無鎖隊列的算法之前,需要先了解一下CAS(compare and swap)的原理。由于多個線程同時操作同一個數據,其中肯定是存在競爭的,那么如何能夠針對同一個數據進行操作,而且又不用加鎖呢? 這個就需要從底層,CPU層面支持原子修改操作,比如在X86的計算機平臺,提供了XCHG指令,能夠原子的交互數值。
從開發語言的層面,比如C++11中,就提供了atomic_compare_exchange_weak函數,來實現CAS。
1. lockless queue,enqueue,dequeue操作的算法
(1) enqueue算法:
enqueue時,先將需要加入隊尾的數據創建出來,然后在一個循環操作中,將數據加入隊尾,如果加入失敗,那么就更新當前的隊尾指針,直到加入成功,然后循環結束。最后調整隊尾指針。
(2) dequeue算法
dequeue時,在循環操作中,使用CAS將隊列頭指針,變成頭指針的下一個指針,如果失敗,持續操作直到成功。最后返回頭指針指向的值。
2. ABA問題,及解決辦法
從上面的算法中,可以看出采用CAS方式實現的無鎖隊列的算法過程,不過由于CAS操作本身的特殊性(通過判斷當前被變換的值,是否發生過變化),可能會在某些情況下引起ABA問題。
那么首先什么是ABA問題呢? wiki上有這樣一個說明的例子:
Natalie is waiting in her car at a red traffic light with her children. Her children start fighting with each other while waiting, and she leans back to scold them. Once their fighting stops, Natalie checks the light again and notices that it's still red. However, while she was focusing on her children, the light had changed to green, and then back again. Natalie doesn't think the light ever changed, but the people waiting behind her are very mad and honking their horns now.
意思就是說,Natalie在等紅燈的時候,由于回頭管孩子,錯過了綠燈,等她再回過頭看信號燈的時候,又是紅燈了。
這其實就是一個ABA問題,雖然中間信號燈發生了變化,但是Natalie卻不知道。
用C++中的一個stack來說明,stack代碼如下:
/* Naive lock-free stack which suffers from ABA problem.*/
class Stack {
std::atomic<Obj*> top_ptr;
//
// Pops the top object and returns a pointer to it.
//
Obj* Pop() {
while(1) {
Obj* ret_ptr = top_ptr;
if (!ret_ptr) return std::nullptr;
// For simplicity, suppose that we can ensure that this dereference is safe
// (i.e., that no other thread has popped the stack in the meantime).
Obj* next_ptr = ret_ptr->next;
// If the top node is still ret, then assume no one has changed the stack.
// (That statement is not always true because of the ABA problem)
// Atomically replace top with next.
if (top_ptr.compare_exchange_weak(ret_ptr, next_ptr)) {
return ret_ptr;
}
// The stack has changed, start over.
}
}
//
// Pushes the object specified by obj_ptr to stack.
//
void Push(Obj* obj_ptr) {
while(1) {
Obj* next_ptr = top_ptr;
obj_ptr->next = next_ptr;
// If the top node is still next, then assume no one has changed the stack.
// (That statement is not always true because of the ABA problem)
// Atomically replace top with obj.
if (top_ptr.compare_exchange_weak(next_ptr, obj_ptr)) {
return;
}
// The stack has changed, start over.
}
}
};
假設,stack初始化為top→ A → B → C
線程1先執行
ret = A; next = B;
然后在線程1執行compare_exchange_weak之前被中斷,換成線程2執行。
{ // 線程2先pop出A
ret = A;
next = B;
compare_exchange_weak(A, B) // Success, top = B
return A;
} // Now the stack is top → B → C
{ // 線程2再pop出B
ret = B;
next = C;
compare_exchange_weak(B, C) // Success, top = C
return B;
} // Now the stack is top → C
delete B;
{ // 最后線程2將A再push進stack
A->next = C;
compare_exchange_weak(C, A) // Success, top = A
}
當線程2執行完所有這些操作之后,換成線程1執行,線程1的compare_exchange_weak是會成功執行的,因為它不知道top_ptr已經被修改過了。
通常針對ABA問題的解決辦法,就是針對操作的數據加上一個原子操作的使用計數,在CAS執行之前,先獲取一下計數是否和之前一樣,如果不一樣,就說明數據已經被修改過了。
總結
以上是生活随笔為你收集整理的无锁队列以及ABA问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蜜汁自信是什么意思 蜜汁自信表情包大全
- 下一篇: 神界3原罪蘑菇怎么打