2016-09-16 53 views
4

在題爲「C++併發在行動」,由安東尼·威廉姆斯,在7.2.1節的書,無鎖堆棧實現上市:爲什麼在這個無鎖棧類中'刪除'節點會導致競爭狀態?

template <typename T> 
class lock_free_stack { 
    struct node { 
     shared_ptr<T> data_; 
     node* next_; 
     node(const T& data) : data_(make_shared(data)) {} 
    }; 
    atomic<node*> head_; 
public: 
    void push(const T& data) 
    { 
     node* new_node = new node(data); 
     new_node->next_ = head_.load(); 
     while(!head.compare_exchange_weak(new_node->next_, new_node)); 
    } 
    shared_ptr<T> pop() 
    { 
     node* old_head = head_.load(); 
     while (old_head && 
       !head_.compare_exchange_weak(old_head, head_->next_)); 
     return old_head ? old_head->data_ : shared_ptr<T>(); 
    } 
}; 

然後在第7.2.2節中,作者說」。 ..在pop()中,我們選擇泄漏節點,以避免一個線程刪除一個節點時的競爭狀態,而另一個線程仍然持有一個指向它的指針,它正要解除引用。

1)我不明白爲什麼這樣的情況會發生,爲什麼彈出以下()函數將導致競爭條件:

shared_ptr<T> pop() 
{ 
    node* old_head = head_.load(); // (1) 
    while (old_head && 
      !head_.compare_exchange_weak(old_head, head_->next_)); // (2) 
    shared_ptr<T> res; // (3) 
    if (old_head) { 
     res.swap(old_head->data); 
     delete old_head; 
     return res; 
    } else { 
     return {}; 
    } 
} 

2)如何來,對於調用彈出多個線程()同時,'old_head'變量可以在第(3)行之後指向同一個節點對象?

回答

8

線程1進入(2)。它開始評估head_->next。它將head_加載到一個寄存器中,然後放棄優先級。

線程2從函數的開始到結束進行。從存在刪除head_刪除它並返回head_的內容。

線程1醒來。它遵循head_在獲得->next字段的註冊表中。但是線程2已經刪除了head_指向的數據,我們只是跟着一個懸掛指針。

+0

對不起,我還不清楚。是的,線程1將遵循不存在的指針並在「next」中讀取可能已經分配給另一個變量的值。因此,compare_exchange_weak()的第二個參數將無效。但它不會被使用,因爲當執行compare_exchange_weak()時,它會檢測到「head_!= old_head」。 所以,是的,我們將讀取compare_exchange_weak()的無效參數,但它將永遠不會被使用。問題是什麼?你能澄清一下嗎? – Alex

+0

@alex你爲什麼會認爲它們不相等?你跟着一個懸掛指針,值可以是*任何*。它在哪裏檢查'head _!= old_head_'?我錯過了嗎? – Yakk

+0

很可能我錯過了一些東西,但我仍然不明白究竟是什麼。 檢查「head _!= old_head_」位於compare_exchange_weak()內部。如果這些值不相等,則不會使用compare_exchange_weak()的第二個參數。 在我的理解compare_exchange_weak()從內存原子讀取頭的當前值。在第一個線程喚醒後的討論場景中,寄存器中的頭值不會等於內存中頭的當前值。因此,compare_exchange_weak()將會看到它,並且不會使用第二個參數(垃圾,我們在註冊表頭中的無效值之後讀取)。 – Alex

相關問題