У меня есть некоторые сомнения относительно вашей tagged_ptr
реализации.Кроме того, у меня есть некоторые сомнения относительно этой части кода:
} else if(itr.is_void()) {
// Thread interfered iteration.
before = &head;
itr = before->load(std::memory_order_acquire);
Допустим, поток удалил последний узел (у вас был 1 узел в списке, и оба потока вызвали стирание).Оставшаяся нить будет запрашивать указатель на голову, это void.Вы войдете в бесконечный цикл с этой частью кода, поскольку она находится в цикле while(itr)
.
Эта часть также не является атомарной:
// Point before-ptr to after. (set head or previous node's next ptr)
if(before->compare_exchange_strong(itr, itr(after))) {
// Set node->next to invalid ptr.
// list iterators will see it and restart their operation.
while(!node->next.compare_exchange_weak(after, after().set_void()))
;
return true;
}
Если before
измененопо первому CAS ваш node
является указателем без привязки, все еще указывающим на список.Еще один поток может установить before
на этот node
, изменить его и вернуть.Честно говоря, если ваш список циклический, его не так сложно отладить, просто пройдите под отладчиком и следуйте списку.Вы увидите, когда он зациклится, и вы сможете выяснить, как он это сделал.Для этого вы также можете использовать valgrind.
Класс tagged_ptr
трудно понять, с помощью метода "set_void ()", который устанавливает для внутреннего ptr
значение 0xFF..F
, но в то же время используется логический тест while (itr) вернет true, если это "void".Я предполагаю, что имя должно быть неверно , а не void , и оно должно возвращать false в операторе bool, если оно (не соответствует действительности).Если itr становится void (это возможно в приведенном выше коде, насколько я понимаю), while(itr)
будет зацикливаться бесконечно.
Например, допустим, у вас было:
Head:A -> B -> C
Затем, после удаления некоторого потока, вы получаете
Thread 2 removing C : Head:A, before = &B on first iteration, exiting the while(itr) loop since itr == C (scheduled here)
Thread 1 removing B : Head:A->C and B->C (scheduled just before line 286 of your example)
Thread 2 resume, and will modify B to B->null (line 283)
and then C->null to C->yourVoid (line 286, then it's scheduled)
Then, thread 1 update B->next to B->yourVoid (useless here for understanding the issue)
You now have A->C->yourVoid
Всякий раз, повторяя здесь, вы получите бесконечный циклтак как, когда поиск itr прибывает на C, следующий шаг должен быть «пустым», а перезапуск итерации с head здесь ничего не решает, это даст тот же результат, список не работает.