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...