这种原子的使用是否正确?
Is this use of atomic correct?
我有以下层次结构:
struct Point
{
std::atomic<int> x;
std::atomic<int> y;
std::atomic<int> z;
}
class Data
{
std::atomic<int> version;
Point point;
}
unordered_map<std::string,Data> hashtable; // global
Thread1 是生产者:
线程 1 收到 std::pair<std::string,Point> new_data
,它确实:
shared_hashtable[new_data.first].point.x.store(new_data.second.x,memory_order_release) // update X
shared_hashtable[new_data.first].point.y.store(new_data.second.y,memory_order_release) // update Y
shared_hashtable[new_data.first].point.z.store(new_data.second.z,memory_order_release) // update Z
hashtable[new_data.first].version.store(hashtable[new_data.first].version+1,memory_order_release) // update the version of the data
线程 2 执行:
int local_version;
while(local_version!=shared_hashtable["string1"].version.load(memory_order_acquire))
{
//...process data...
local_version=shared_hashtable["string1"].version.load(memory_order_acquire);
}
传递的内存顺序是否保证我的存储和加载不会被重新排序。
设计是否按预期工作:如果更新 hastable["string1"]
处的对象,我会在线程 2 中处理相应的数据吗?
hashtable
不受同时访问保护,其 operator[]
不是 const
。如果您保证 (1) hashtable["string1"]
在这两个线程尝试访问它之前插入到 table 中,并且 (2) 在这两个期间没有其他线程写入 hashtable
线程是 运行,hashtable
中的查找不会导致数据争用。如果您不能保证 (1) 和 (2),那么您需要使用互斥体来保护查找。 unordered_map
重新散列时使迭代器无效,但引用保持有效,直到引用项从映射中删除。
内存模型保证线程 1 中在 memory_order_release
写入 version
之前的写入对于线程 2 在 memory_order_acquire
从 [=] 读取相应值后是可见的21=]。即使对 Point
成员的访问是非原子的,情况也是如此。
但是,线程 2 中 Point
成员的读取可能会看到线程 1 中 later 写入的值,因此不能保证在线程 2 中读取的三个 Point
成员对应于线程 1 实际写入的特定 Point
。我想你需要保证线程 2 处理的 Point
实际上是一些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
可能已经被 later 版本覆盖在线程 2 开始阅读它时线程 1。这可能会导致同一点被线程 2 处理两次,一次使用陈旧版本,然后在稍后的迭代中使用正确版本再次处理。如果需要确保每个版本最多处理一次,则必须验证 version
在 读取 point
后与 [=54] 相同 =]之前阅读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...
}
}
}
}
你可以试试
https://github.com/Taymindis/atomic_hashtable
当多线程在缓冲区、简单和 Stable
上执行操作时,无需锁定即可读取、写入和删除的散列 table
API Readme.md
中给出的文件
我有以下层次结构:
struct Point
{
std::atomic<int> x;
std::atomic<int> y;
std::atomic<int> z;
}
class Data
{
std::atomic<int> version;
Point point;
}
unordered_map<std::string,Data> hashtable; // global
Thread1 是生产者:
线程 1 收到 std::pair<std::string,Point> new_data
,它确实:
shared_hashtable[new_data.first].point.x.store(new_data.second.x,memory_order_release) // update X
shared_hashtable[new_data.first].point.y.store(new_data.second.y,memory_order_release) // update Y
shared_hashtable[new_data.first].point.z.store(new_data.second.z,memory_order_release) // update Z
hashtable[new_data.first].version.store(hashtable[new_data.first].version+1,memory_order_release) // update the version of the data
线程 2 执行:
int local_version;
while(local_version!=shared_hashtable["string1"].version.load(memory_order_acquire))
{
//...process data...
local_version=shared_hashtable["string1"].version.load(memory_order_acquire);
}
传递的内存顺序是否保证我的存储和加载不会被重新排序。
设计是否按预期工作:如果更新 hastable["string1"]
处的对象,我会在线程 2 中处理相应的数据吗?
hashtable
不受同时访问保护,其 operator[]
不是 const
。如果您保证 (1) hashtable["string1"]
在这两个线程尝试访问它之前插入到 table 中,并且 (2) 在这两个期间没有其他线程写入 hashtable
线程是 运行,hashtable
中的查找不会导致数据争用。如果您不能保证 (1) 和 (2),那么您需要使用互斥体来保护查找。 unordered_map
重新散列时使迭代器无效,但引用保持有效,直到引用项从映射中删除。
内存模型保证线程 1 中在 memory_order_release
写入 version
之前的写入对于线程 2 在 memory_order_acquire
从 [=] 读取相应值后是可见的21=]。即使对 Point
成员的访问是非原子的,情况也是如此。
但是,线程 2 中 Point
成员的读取可能会看到线程 1 中 later 写入的值,因此不能保证在线程 2 中读取的三个 Point
成员对应于线程 1 实际写入的特定 Point
。我想你需要保证线程 2 处理的 Point
实际上是一些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
可能已经被 later 版本覆盖在线程 2 开始阅读它时线程 1。这可能会导致同一点被线程 2 处理两次,一次使用陈旧版本,然后在稍后的迭代中使用正确版本再次处理。如果需要确保每个版本最多处理一次,则必须验证 version
在 读取 point
后与 [=54] 相同 =]之前阅读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...
}
}
}
}
你可以试试 https://github.com/Taymindis/atomic_hashtable
当多线程在缓冲区、简单和 Stable
上执行操作时,无需锁定即可读取、写入和删除的散列 tableAPI Readme.md
中给出的文件