關於 lockfree 演算法

2021-08-23 14:57:16 字數 1356 閱讀 8674

lockfree的本質是樂觀鎖。也就是說,它假設多數情況下,別人不會改變。乙個通用的lockfree演算法可描述如下:

lockfree_modify(datat* data)}}

可以看出,lockfree也是鎖,只是將鎖限制在乙個最小的範圍內(通常是乙個原子操作)。由於仍然有鎖,lockfree在多核下並不會比普通的鎖高明多少,它也不能隨cpu個數增加而獲得呈線性scale的效能提公升。

不過,lockfree有個最大的好處,就是不可能有死鎖。因為對上面演算法分析你可以知道,在某個執行緒modify失敗,總意味著有另乙個人成功了,整個程式總在一步步前進,而不是出現相互等待而產生飢餓的狀況。

我們以stack為例,展示下lockfree的stack是怎樣的:

class stack

;private:

node* m_head;

public:

stack()

: m_head(null)

void push(node* val)

}node* pop() }

};嗯,看起來挺不錯,**還算優雅。。。不過遺憾的是,嚴謹的說其實上面的lockfree演算法是有問題的。問題在**?在pop演算法上。我們看lock範圍內的語義:

if (interlockedcompareexchangepointer((pvoid*)&m_head, head->prev, head) == head)

return head;

這句話用偽**描述是:

lock

}問題在於,m_head指標的值沒有發生變化,和整個stack沒有發生變化,這兩者等價嗎?

不等價。完全可以發生一種情況就是,另乙個執行緒執行了這樣一段**:

v1 = pop(); // v1是m_head

v2 = pop();

push(v1);

此時,m_head沒有變化,但是m_head->prev不再是v2,而是v2->prev了。

那麼怎麼辦?在說解決方案前,我們先再聊下上例中的stack::push。既然stack::pop有問題,那麼stack::push呢?我們說,push演算法的並沒有什麼問題。儘管同樣的,m_head指標的值沒有發生變化,並不意味著整個stack沒有發生變化。但是對於push來說,它只關心m_head,而不關心其他東西,所以stack是否發生變化對其並無影響。

那麼,應該如何解決pop演算法遇到的問題?乙個比較通用的方法,就是版本號。lockfree演算法中,有乙個術語叫tagged_ptr,其實本質上就是乙個打了版本號的pointer:

struct tagged_ptr

;每次要對p進行修改,都++tag。這樣,判斷是不是modify,只需要判斷tag是否變化即可。

lockfree小結:

Lock Free?還是多入口?

最近一段時間,感覺大家對於lock free的興趣又高漲了起來,lock free大有包治百病 一統江湖之勢,特寫下此文,希望對圍觀者有所幫助。讓我們先從乙個簡單的場景開始 考慮乙個需要頻繁併發訪問的freelist,這應該是許多應用程式中最常見的結構了,如果我們使用基本設計,用乙個簡單的mutex...

無鎖程式設計 lock free原理

無鎖程式設計是指在不使用鎖的情況下,在多執行緒環境下實現多變數的同步。即在沒有執行緒阻塞的情況下實現同步。這樣可以避免競態 死鎖等問題。cas是指compare and swap或compare and set cas是乙個原子操作,用於多執行緒環境下的同步。它比較記憶體中的內容和給定的值,只有當兩...

lock free 實現多執行緒安全鍊錶

lock free 實現的多執行緒鍊錶通常無法避免 aba問題。aba 問題的實質就是我們剛釋放的記憶體可能會被馬上又分配出來,被其他執行緒又放入到煉表裡了,導致interlock函式判斷鍊錶節點沒有改變 實際上節點已經被刪除過一次了,鍊錶發生了改變 而導致錯誤。那麼解決方法最有3中 一種不使用 i...