Я проводил некоторое исследование реализаций STM (программной транзакционной памяти), в частности алгоритмов, которые используют блокировки и не зависят от наличия сборщика мусора, чтобы поддерживать совместимость с не -управляемые языки, такие как C / C ++. Я прочитал главу о STM в книге Херлихи и Шавита «Искусство многопроцессорного программирования» , а также прочитал пару статей Шавита, в которых описываются его «Транзакционная блокировка» и ] «Транзакционная блокировка II» Реализации STM. Их основной подход заключается в использовании хэш-таблицы, в которой хранятся значения глобальных часов версии и блокировки, чтобы определить, была ли затронута область памяти при записи другого потока. Насколько я понимаю алгоритм, когда выполняется транзакция записи, часы версии считываются и сохраняются в локальной памяти потока, а набор чтения и записи также создаются в локальной памяти потока. Затем выполняются следующие шаги:
Если какой-либо из вышеуказанных шагов проверки завершился неудачно (то есть шаги №1, №3 и №5), то транзакция записи прерывается.
Процесс чтения-транзакции намного проще. Согласно документам Шавита, мы просто
Если один из этапов №2 или №4 терпит неудачу, то транзакция чтения прерывается.
Вопрос, который я, кажется, не могу решить в уме, заключается в том, что происходит, когда вы пытаетесь прочитать область памяти внутри объекта, который находится в куче, а другой поток вызывает delete
на указатель на этот объект? В статьях Шавита они подробно объясняют, как не может быть записи в область памяти, которая была переработана или освобождена, но кажется, что внутри транзакции чтения нет ничего, что препятствовало бы возможному сценарию синхронизации, который позволил бы вам для чтения из области памяти внутри объекта, освобожденной другим потоком. В качестве примера рассмотрим следующий код:
Поток A
выполняет следующее внутри атомарной транзакции чтения: connected_list_node * next_node = node-> next;
Thread B
выполняет следующее: удалить узел;
Поскольку next_node
является локальной переменной потока, это не транзакционный объект. Операция разыменования, необходимая для присвоения ему значения node-> next
, на самом деле требует двух отдельных чтений. В промежутках между этими чтениями delete
может быть вызвано на узле
, так что чтение из элемента next
фактически считывается из сегмента памяти, который уже был освобожден. Поскольку чтения оптимистичны, освобождение памяти, на которую указывает узел
в потоке B
, не будет обнаружено в потоке A
.Не вызовет ли это возможный сбой или ошибку сегментации? Если да, то как этого можно избежать, не блокируя также и ячейки памяти для чтения (то, что отмечается как в учебнике, так и в статьях, не является необходимым)?