Реализация самой функции 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(p);

}

struct data_to_reclaim {

 void* data;

 std::function deleter;

 data_to_reclaim* next;

 template

 data_to_reclaim(T* p) : ←(1)

  data(p),

  deleter(&do_delete), next(0) {}

 ~data_to_reclaim() {

  deleter(data); ←(2)

 }

};

std::atomic nodes_to_reclaim;

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;

 }

}

Перейти на страницу:

Похожие книги