В C ++ Concurrency in Action, 2e, автор описывает реализацию связанного списка без блокировки потоков. Прямо сейчас он описывает метод pop () и то, как безопасно удалить узлы в своего рода методе «сборщика мусора», чтобы убедиться, что никакие другие потоки не вызывают pop в том же экземпляре. Вот часть этого кода для pop:
#include <atomic>
#include <memory>
template<typename T>
class lock_free_stack
{
private:
std::atomic<unsigned> threads_in_pop;
void try_reclaim(node* old_head);
public:
std::shared_ptr<T> pop()
{
++threads_in_pop;
node* old_head=head.load();
while(old_head &&
!head.compare_exchange_weak(old_head,old_head->next));
std::shared_ptr<T> res;
if(old_head)
{
res.swap(old_head->data);
}
try_reclaim(old_head);
return res;
}
};
Важно то, что счетчик атомарно увеличивается каждый раз при вызове pop (). Затем функция try_reclaim уменьшит значение счетчика. Вот реализация try_reclaim:
void try_reclaim(node* old_head)
{
if(threads_in_pop==1) //#1
{
node* nodes_to_delete=to_be_deleted.exchange(nullptr);
if(!--threads_in_pop) //#2
{
delete_nodes(nodes_to_delete);
}
else if(nodes_to_delete)
{
chain_pending_nodes(nodes_to_delete);//#3
}
delete old_head; //#4 THIS IS THE PART I AM CONFUSED ABOUT
}
else
{
chain_pending_node(old_head);
--threads_in_pop;
}
}
Реализации других функций, вызываемых здесь, не имеют значения (они просто добавляют узлы в цепочку узлов, подлежащих удалению), поэтому я их пропустил. Часть кода, которая меня запутала, - это №4 (отмечена). Здесь автор вызывает delete для переданного old_head. Однако почему он не проверяет, является ли значение thread_in_pop по-прежнему равным нулю в этот момент, прежде чем удалять old_head? Он дважды проверяет строки №2 и №1, чтобы убедиться, что другой поток в настоящее время не находится в pop (), так почему он не проверяет еще раз, прежде чем продолжить удаление old_head? Не мог ли другой поток вызвать pop () сразу после # 3, увеличив таким образом счетчик, и к тому времени, когда первый поток достигнет # 4, thread_in_pop больше не будет равным нулю?
Другими словами, не могло ли быть возможным, чтобы thread_in_pop было, например, 2, к тому времени, когда код достигнет # 4? Как он мог в таком случае безопасно удалить old_head? Может кто-нибудь объяснить, пожалуйста?