Я не думаю, что у вас есть C++ UB от параллельного доступа к неатомным данным, но похоже, что у вас есть the normal kind of race condition в вашем коде.
if (x==a) x = b
почти всегда должен быть атомарным чтение-модификация-запись (вместо отдельных атомных нагрузок и атомных магазинов) в алгоритмах безблокировочного, если нет какой-то причине, почему это нормально по-прежнему хранить b
если x
изменено на что-то другое чем a
между чеком и магазином.
(В этом случае единственное, что может быть сохранено, это значение, которое уже было там, как указывает @MargaretBloom. Таким образом, нет «ошибки», просто куча бесполезных магазинов, если это единственный код что касается массива. Я предполагаю, что вы на самом деле не намерены писать бесполезный пример, поэтому я рассматриваю это ошибку.)
программирования безблокировочного не так просто, даже если вы сделайте это низкопроизводительным способом со значением по умолчанию std::memory_order_seq_cst
для всех магазинов, поэтому компилятор должен MFENCE
всюду. Создание всего atomic
позволяет избежать C++ UB; вам все равно придется тщательно разрабатывать логику вашего алгоритма, чтобы убедиться, что она правильная, даже если несколько магазинов/загрузок из других потоков (потоков) становятся видимыми между каждой вашей собственной операцией и т. д. (например, см. Preshing's lock-free hash table.) .)
Необходимо быть свободным от UB (по крайней мере теоретически), но определенно недостаточно для правильного/безопасного кода. Быть без гонок означает отсутствие (проблематичных) рас даже между атомными доступами. Это более сильная, но все же недостаточная часть отсутствия ошибок.
Я говорю «в теории», потому что на практике много кода с UB происходит, чтобы скомпилировать то, что мы ожидаем, и будем укусить вас только на других платформах или с будущими компиляторами или с другим окружающим кодом, который предоставляет UB во время оптимизации.
Тестирование не может легко обнаружить все ошибки, особенно. если вы только тестируете на сильно упорядоченном оборудовании x86, но простая ошибка, подобная этому, должна быть легко обнаружима при тестировании.
Проблема с кодом, более подробно:
Вы делаете неатомарные сравнения-обмен, с атомной нагрузкой и отдельным атомным магазином:
if(hazard_pointers[i].id.load() == id){
// a store from another thread can become visible here
hazard_pointers[i].id.store(id);
return hazard_pointers[i].hp;
}
.store()
должен быть std::compare_exchange_strong
, поэтому значение не изменяется, если хранилище из другого потока изменило значение между вашей загрузкой и вашим магазином. (Помещение внутри if
на расслабленную или приобретаемую нагрузку по-прежнему является хорошей идеей, я думаю, что ветка, чтобы избежать lock cmpxchg
, является хорошей идеей, если вы ожидаете, что значение не будет соответствовать большую часть времени. Это должно позволить оставаться в кеш-строках Shared, когда нить не находит совпадение по этим элементам.)
Можете ли вы разместить CWME? Как это называется? Являются ли идентификатор danger_pointer установленным до вызова этой функции? Существует ли барьер между ними и эта функция называется? – Tim
Я добавил пример использования. Ваши другие вопросы не имеют значения в этом контексте. – Gilgamesz
Возвращает ссылку на локальную переменную - неопределенное поведение. –