Реализация самой функции get_hazard_pointer_for_current_thread() обманчиво проста (3): в ней объявлена переменная типа hp_owner в поточно-локальной памяти (4), в которой хранится принадлежащий данному потоку указатель опасности. Затем она просто возвращает полученный от этого объекта указатель (5). Работает это следующим образом: в первый раз, когда hp_owner. Его конструктор (1) ищет в таблице пар (владелец, указатель) незанятую запись (такую, у которой нет владельца). На каждой итерации цикла он с помощью compare_exchange_strong() атомарно выполняет два действия: проверяет, что у текущей записи нет владельца, и делает владельцем себя (2). Если compare_exchange_strong() возвращает false, значит, записью владеет другой поток, поэтому мы идем дальше. Если же функция вернула true, то мы успешно зарезервировали запись для текущего потока, поэтому можем сохранить ее адрес и прекратить поиск (3). Если мы дошли до конца списка и не обнаружили свободной записи (4), значит, потоков, использующих указатель опасности, слишком много, так что приходится возбуждать исключение.
После того как экземпляр hp_owner для данного потока создан, последующие обращения происходят гораздо быстрее, потому что указатель запомнен и просматривать таблицу снова нет нужды.
Когда завершается поток, для которого был создан объект hp_owner, этот объект уничтожается. Прежде чем сохранить в идентификаторе владельца значение std::thread::id(), деструктор записывает в сам указатель значение nullptr, чтобы другие потоки могли повторно использовать эту запись. При такой реализации get_hazard_pointer_for_current_thread() реализация функции outstanding_hazard_pointers_for() совсем проста: требуется только найти переданное значение в таблице указателей опасности:
bool outstanding_hazard_pointers_for(void* p) {
for (unsigned i = 0; i < max_hazard_pointers; ++i) {
if (hazard_pointers[i].pointer.load() == p) {
return true;
}
}
return false;
}
He нужно даже проверять, есть ли у записи владелец, так как в бесхозных записях все равно хранятся нулевые указатели, поэтому сравнение заведомо вернёт false; это еще упрощает код. Теперь функции reclaim_later() и delete_nodes_with_no_hazards() могут работать с простым связанным списком; reclaim_later() добавляет в него узлы, a delete_nodes_with_no_hazards() удаляет узлы, на которые не ссылаются указатели опасности. Реализация обеих функций приведена в следующем листинге.
Листинг 7.8. Простая реализация функций освобождения узлов
template
void do_delete(void* p) {
delete static_cast
}
struct data_to_reclaim {
void* data;
std::function
data_to_reclaim* next;
template
data_to_reclaim(T* p) : ←(1)
data(p),
deleter(&do_delete
~data_to_reclaim() {
deleter(data); ←(2)
}
};
std::atomic
void add_to_reclaim_list(data_to_reclaim* node) {←(3)
node->next = nodes_to_reclaim.load();
while (
!nodes_to_reclaim.compare_exchange_weak(node->next, node));
}
template
void reclaim_later(T* data) { ←(4)
add_to_reclaim_list(new data_to_reclaim(data));←(5)
}
void delete_nodes_with_no_hazards() {
data_to_reclaim* current =
nodes_to_reclaim.exchange(nullptr); ←(6)
while(current) {
data_to_reclaim* const next = current->next;
if (!outstanding_hazard_pointers_for(current->data)) {←(7)
delete current; ←(8)
} else {
add_to_reclaim_list(current); ←(9)
}
current = next;
}
}