В книге под названием «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)?
Извините, мне все еще не ясно. Да, поток 1 будет следовать отсутствующему указателю и читать значение в «следующем», которое, вероятно, уже выделено для другой переменной. Итак, второй аргумент для compare_exchange_weak() будет недействительным. Но он никогда не будет использоваться, потому что, когда compare_exchange_weak() будет выполнен, он обнаружит это «head_! = Old_head». Итак, да, мы будем читать недопустимый аргумент для compare_exchange_weak(), но он никогда не будет использоваться. В чем проблема? Не могли бы вы прояснить? – Alex
@alex Почему вы предполагаете, что они не равны? Вы следовали за висячим указателем, значение может быть * ничего *. И где он проверяет 'head _! = Old_head_'? Я пропустил это? – Yakk
Скорее всего, я что-то упускаю, но я до сих пор не могу понять, что именно. Проверьте, что «head_! = Old_head_» находится внутри compare_exchange_weak(). Если эти значения не равны, второй аргумент compare_exchange_weak() не будет использоваться. В моем понимании compare_exchange_weak() атомарно считывает текущее значение головки из памяти. В обсуждаемом сценарии после пробуждения 1-го потока значение головки в регистре не будет равно текущему значению головы в памяти. Таким образом, compare_exchange_weak() увидит это и не будет использовать второй аргумент (мусор, который мы читаем после недопустимого значения head в регистре). – Alex