[英]Looking for critique of my thread safe, lock-free queue implementation
所以,經過一番研究后,我寫了一個隊列。 它使用固定大小的緩沖區,因此它是一個循環隊列。 它必須是線程安全的,我試圖讓它無鎖。 我想知道它有什么問題,因為這些事情我自己很難預測。
這是標題:
template <class T>
class LockFreeQueue
{
public:
LockFreeQueue(uint buffersize) : buffer(NULL), ifront1(0), ifront2(0), iback1(0), iback2(0), size(buffersize) { buffer = new atomic <T>[buffersize]; }
~LockFreeQueue(void) { if (buffer) delete[] buffer; }
bool pop(T* output);
bool push(T input);
private:
uint incr(const uint val)
{return (val + 1) % size;}
atomic <T>* buffer;
atomic <uint> ifront1, ifront2, iback1, iback2;
uint size;
};
這是實施:
template <class T>
bool LockFreeQueue<T>::pop(T* output)
{
while (true)
{
/* Fetch ifront and store it in i. */
uint i = ifront1;
/* If ifront == iback, the queue is empty. */
if (i == iback2)
return false;
/* If i still equals ifront, increment ifront, */
/* Incrememnting ifront1 notifies pop() that it can read the next element. */
if (ifront1.compare_exchange_weak(i, incr(i)))
{
/* then fetch the output. */
*output = buffer[i];
/* Incrememnting ifront2 notifies push() that it's safe to write. */
++ifront2;
return true;
}
/* If i no longer equals ifront, we loop around and try again. */
}
}
template <class T>
bool LockFreeQueue<T>::push(T input)
{
while (true)
{
/* Fetch iback and store it in i. */
uint i = iback1;
/* If ifront == (iback +1), the queue is full. */
if (ifront2 == incr(i))
return false;
/* If i still equals iback, increment iback, */
/* Incrememnting iback1 notifies push() that it can write a new element. */
if (iback1.compare_exchange_weak(i, incr(i)))
{
/* then store the input. */
buffer[i] = input;
/* Incrementing iback2 notifies pop() that it's safe to read. */
++iback2;
return true;
}
/* If i no longer equals iback, we loop around and try again. */
}
}
編輯:我根據評論(感謝KillianDS和nm!)對代碼做了一些重大修改。 最重要的是,ifront和iback現在是ifront1,ifront2,iback1和iback2。 push()現在將遞增iback1,通知其他推送線程,它們可以安全地寫入下一個元素(只要它不是滿的),寫入元素,然后遞增iback2。 iback2是pop()檢查的全部內容。 pop()執行相同的操作,但使用ifrontn索引。
現在,再一次,我陷入了“這應該工作......”的陷阱,但我對形式證明或類似的東西一無所知。 至少這次,我想不出它可能失敗的潛在方式。 除了“停止嘗試編寫無鎖代碼”之外,任何建議都表示贊賞。
接近無鎖數據結構的正確方法是編寫一個半正式證明,證明您的設計在偽代碼中工作。 你不應該問“這個鎖定免費代碼線程是否安全”,而是“我證明這個鎖定免費代碼是線程安全的有什么錯誤嗎?”
只有在您獲得偽代碼設計有效的正式證據之后,才會嘗試實現它。 通常這會帶來垃圾收集等必須小心處理的問題。
您的代碼應該是注釋中的形式證明和偽代碼,其中散布着相對不重要的實現。
驗證代碼是否正確則需要了解偽代碼,檢查證明,然后檢查代碼是否無法映射到偽代碼和證明。
直接獲取代碼並嘗試檢查它是否鎖定是不切實際的。 證明是正確設計這類事物的重要因素,實際的代碼是次要的,因為證明是困難的部分。
在完成上述所有操作之后 , 當其他人驗證它時,您必須通過實際測試來驗證代碼是否存在盲點並且存在漏洞,或者不了解並發原語,或者如果你的並發原語中有bug。
如果您對編寫半正式證明不感興趣來設計代碼,則不應手動鎖定無鎖算法和數據結構,並將它們放在生產代碼中。
確定一堆代碼“是否是線程安全的”將所有工作負擔都放在其他人身上。 你需要有一個論點,為什么你的代碼“是線程安全的”安排,以便其他人盡可能容易地找到它的漏洞。 如果你的論點為什么你的代碼“是線程安全的”被安排的方式使得更難找到漏洞,那么你的代碼就不能被認為是線程安全的,即使沒有人能在你的代碼中發現漏洞。
你上面發布的代碼很亂。 它包含注釋掉的代碼,沒有正式的不變量,沒有線條的證明,沒有強烈描述為什么它是線程安全的,並且一般不提出嘗試以一種易於發現的方式顯示自己作為線程安全缺陷。 因此,沒有合理的讀者會認為代碼線程是安全的,即使他們在其中找不到任何錯誤。
不,它不是線程安全的 - 如果發生事件,請考慮以下順序:
if (ifront.compare_exchange_weak(i, incr(i)))
在pop
並由調度程序進入休眠狀態。 size
時間(剛好足以使ifront等於第一個線程中的i
值)。 在這種情況下,pop buffer[i]
將包含最后一個推送值,這是錯誤的。
在考慮環繞時存在一些問題,但我認為代碼的主要問題是它可能會從緩沖區中彈出無效值。
考慮一下:
ifront = iback = 0
推送被調用,CAS增加了iback 0 - > 1的值。但是,在分配緩沖區[0]之前,線程現在停止了。
ifront = 0,iback = 1
Pop現在被稱為。 CAS增加ifront 1 - > 1並且緩沖區[0]在分配之前被讀取。
彈出過時或無效的值。
PS。 因此,一些研究要求使用DCAS或TCAS(Di和Tri CAS)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.