被连续迭代的线程安全无序映射
Thread-Safe Unordered Map that is being Continuously Iterated Over
我有 classes 的集合,可以使用以下模板访问:
template <typename T> using RegistryMap = std::unordered_map <std::string, T *>;
template <typename T> class Registry {
static RegistryMap<T> registry;
public:
static T* get(const std::string& name) {
auto it = registry.find(name);
return it == registry.end() ? nullptr : it->second;
}
static const RegistryMap<T>& getAll() {
return registry;
}
static bool add(const std::string &name, T *object) {
T* &store = registry[name];
if (store)
return false;
else {
store = object;
return true;
}
}
static bool remove(const std::string &name) {
auto it = registry.find(name);
if (it == registry.end())
return false
else {
registry.erase(it);
return true;
}
}
};
此注册表中的许多 classes 定义了一个名为 运行 的方法,线程线程将调用一个紧密循环。当发生这种情况时,其他线程可能会使用上述方法从地图中 add/remove 个元素。
void workerThread() {
examples = Registry<ExampleClass>::getAll();
auto it = examples.begin();
while (true) {
if (it != examples.end())
it++->second->run();
else
it = examples.begin();
}
}
有没有办法让这个模板线程安全?我可以将静态锁添加到注册表模板 class 并在添加或删除方法中获取它。但是我该如何处理紧密循环的线程呢?特别是在注册表增长非常大的情况下。愿意更换 unordered_map 并在必要时丢失线性查找时间。
有无数种方法。这是一个非常简单的实现错误的可能性很小。
在您的内部实现中,不要保留单个散列 table,而是 p 对散列 table 和锁( p 是某个素数)。
要修改(即插入或删除)一个条目,首先找到它的散列值,然后将它取模到 p table 之一,然后锁定和做手术。
同时,紧环线程将迭代 p 组。对于每个组,它将锁定锁,遍历该组,然后释放它。
p 有一个折衷。 p的值太小(相对对象总数)会导致争锁的概率很高。 p 的值太高将导致紧密循环线程的锁定到有效负载操作的开销很高。
根据情况,您可能还想考虑读写锁,只有修改操作才具有写访问权限。
一种简单的方法是使用循环 linked 列表。这根本不需要锁定 - 只需确保插入和删除是列表本身的原子操作(很容易实现)。
如果线性查找很重要,可以通过在键(字符串...)和列表中的 link 之间保存一个映射来扩展解决方案,以便于删除或任何其他操作。
我有 classes 的集合,可以使用以下模板访问:
template <typename T> using RegistryMap = std::unordered_map <std::string, T *>;
template <typename T> class Registry {
static RegistryMap<T> registry;
public:
static T* get(const std::string& name) {
auto it = registry.find(name);
return it == registry.end() ? nullptr : it->second;
}
static const RegistryMap<T>& getAll() {
return registry;
}
static bool add(const std::string &name, T *object) {
T* &store = registry[name];
if (store)
return false;
else {
store = object;
return true;
}
}
static bool remove(const std::string &name) {
auto it = registry.find(name);
if (it == registry.end())
return false
else {
registry.erase(it);
return true;
}
}
};
此注册表中的许多 classes 定义了一个名为 运行 的方法,线程线程将调用一个紧密循环。当发生这种情况时,其他线程可能会使用上述方法从地图中 add/remove 个元素。
void workerThread() {
examples = Registry<ExampleClass>::getAll();
auto it = examples.begin();
while (true) {
if (it != examples.end())
it++->second->run();
else
it = examples.begin();
}
}
有没有办法让这个模板线程安全?我可以将静态锁添加到注册表模板 class 并在添加或删除方法中获取它。但是我该如何处理紧密循环的线程呢?特别是在注册表增长非常大的情况下。愿意更换 unordered_map 并在必要时丢失线性查找时间。
有无数种方法。这是一个非常简单的实现错误的可能性很小。
在您的内部实现中,不要保留单个散列 table,而是 p 对散列 table 和锁( p 是某个素数)。
要修改(即插入或删除)一个条目,首先找到它的散列值,然后将它取模到 p table 之一,然后锁定和做手术。
同时,紧环线程将迭代 p 组。对于每个组,它将锁定锁,遍历该组,然后释放它。
p 有一个折衷。 p的值太小(相对对象总数)会导致争锁的概率很高。 p 的值太高将导致紧密循环线程的锁定到有效负载操作的开销很高。
根据情况,您可能还想考虑读写锁,只有修改操作才具有写访问权限。
一种简单的方法是使用循环 linked 列表。这根本不需要锁定 - 只需确保插入和删除是列表本身的原子操作(很容易实现)。
如果线性查找很重要,可以通过在键(字符串...)和列表中的 link 之间保存一个映射来扩展解决方案,以便于删除或任何其他操作。