hashtable
не защищен от одновременного доступа, а его operator[]
не является const
. Если вы гарантируете, что (1) hashtable["string1"]
вставлен в таблицу до того, как эти два потока попытаются получить к ней доступ, и (2) никакие другие потоки не записывают в hashtable
за время, когда эти два потока запущены, поиск в hashtable
не приведет к в гонке данных. Если вы не можете гарантировать (1) и (2), вам необходимо защитить поиск с помощью мьютекса. unordered_map
аннулирует итераторы при повторном обращении, но ссылки остаются действительными до тех пор, пока ссылочный элемент не будет удален с карты.
модель Память гарантирует вам, что записи в потоке 1, которые предшествуют memory_order_release
записи в version
видны нити 2 после того, как он memory_order_acquire
считывает соответствующее значение из version
. Это было бы так, даже если доступ к членам Point
был неатомным.
Тем не менее, возможно, что читает из Point
членов в нити 2 значения см от позже записи в потоке 1, так что вы не гарантированы, что три Point
членов читать нить 2 соответствуют конкретной Point
на самом деле это было написано нитью 1. Я полагаю, вам нужна гарантия того, что Point
, обработанный потоком 2, фактически представляет собой Point
, написанный нитью 1, а не совокупность значений нескольких разных точек (например, x
от версии 1, y
от версии 2, z
из версии 3). Изменение дизайна от
struct Point { atomic<int> x, y, x; };
в
struct Point { int x, y, x; };
struct Data {
atomic<int> version;
atomic<Point> point;
};
гарантирует, что Point
чтения на самом деле Point
написано. Конечно, не обязательно Point
, который соответствует данному version
: point
может быть уже перезаписан позже версии в потоке 1 по времени, когда поток 2 приближается к его чтению. Это может привести к тому, что одна и та же точка обрабатывается дважды потоком 2, один раз с устаревшей версией и снова на более поздней итерации с соответствующей версией. Если вам нужно убедиться, что каждая версия обработана не чаще одного раза, вы должны убедиться, что version
- это то же самое после, читающее point
как было до чтение point
.
Собираем все вместе, мы получаем эту программу (DEMO):
struct Point
{
int x, y, z;
};
struct Data
{
std::atomic<int> version;
std::atomic<Point> point;
};
std::unordered_map<std::string, Data> hashtable;
std::mutex mtx;
Data& lookup(const std::string& key) {
std::lock_guard<std::mutex> guard{mtx};
return hashtable[key];
}
void thread1() {
std::string key;
Point point;
std::tie(key, point) = // get new key/point pair
auto& ref = lookup(key);
ref.point.store(point, std::memory_order_relaxed);
ref.version.fetch_add(1, std::memory_order_release);
}
void thread2() {
auto& ref = lookup("string1");
int local_version = 0;
for (;;) {
auto const version = ref.version.load(std::memory_order_acquire);
if (local_version != version) {
auto point = ref.point.load(std::memory_order_relaxed);
if (version == ref.version.load(std::memory_order_acquire)) {
local_version = version;
// ...process point...
}
}
}
}
Я не вижу дизайн whatsoev er, вы играете параллелизм на элементарной сфере, что не хорошо. –
Ну, я хочу, чтобы глобальный объект обновлялся, а другой поток мог улавливать эти обновления. Я пытаюсь сделать это, не создавая новый объект каждый раз, просто обновляя существующий. – user2164703