数组的不同元素上的多个锁
multiple locks on different elements of an array
如果我有8个线程,并且一个数组中有1000000000个元素,那么我可以有1000000000次倍数,其中索引表示数组中被锁定和写入的元素。然而,这对我来说相当浪费,并且需要大量内存。
有没有一种方法可以让我只能使用8个静音并具有相同的功能?
在这里大声思考。。。并且不确定这会有多有效,但是:
您可以创建锁定某些索引的方法:
vector<int> mutexed_slots;
std::mutex mtx;
bool lock_element(int index)
{
std::lock_guard<std::mutex> lock(mtx);
// Check if item is in the mutexed list
if ( !std::find(mutexed_slots.begin(), mutexed_slots.end(), index) != vector.end() )
{
// If its not then add it - now that array value is safe from other threads
mutexed_slots.emplace_back(index);
return true;
}
return false;
}
void unlock_element(int index)
{
std::lock_guard<std::mutex> lock(mtx);
// No need to check because you will only unlock the element that you accessed (unless you are a very naughty boy indeed)
vec.erase(vec.begin() + index);
}
注意:这是一个想法的开始,所以现在不要太用力!它也是未经测试的伪代码。这并不是一个最终的答案,而是一个起点。请添加评论以改进或暗示这是/不合理的。
进一步要点:
- 可能有更高效的STL可供使用
- 您可能会将所有这些与数据一起封装在一个类中
- 您需要循环使用
lock_element()
,直到它返回true——同样,目前还不太好。这一机制可以改进 - 每个线程都需要记住他们目前正在处理哪个索引,这样他们就只能解锁那个特定的索引——同样,这可以在类中进行更多的集成,以确保这种行为
但作为一个概念——可行吗?我想,如果你需要非常快速的访问(也许你确实需要),这可能不会那么有效,你想吗?
更新
如果每个线程/工作线程在mutexed_slots
中"注册"自己的条目,这可能会变得更加高效。然后,向量中就没有push_back/remove了(除了开始/结束处)。因此,每个线程只设置它锁定的索引——如果它什么都没有锁定,那么它只设置为-1(或诸如此类)。我认为还有更多这样的效率改进需要改进。再一次,一个完整的类为您完成所有这些将是实现它的方法
测试/结果
我为此实现了一个测试程序,只是因为我非常喜欢这种东西。我的实现在这里
我认为这是一个公开的github回购,所以欢迎你看一看。但我把结果发布在了顶级自述上(所以滚动一下可以看到它们)。我实施了一些改进,例如:
- 运行时没有对保护阵列的插入/删除
不需要lock_guard来进行"解锁",因为我不依赖std::atomic索引。
以下是我的摘要打印件:
摘要:
当工作量为1ms(执行每个操作所需的时间)时,完成的工作量为:
- 9808受保护
8117用于正常
注意这些值各不相同,有时正常值更高,似乎没有明确的赢家。
当工作负载为0ms(基本上增加了几个计数器)时,完成的工作量为:
- 9791264受保护
- 29307829表示正常
因此,在这里您可以看到,使用互斥保护会将工作速度降低约三分之一(1/3)。这个比率在两次测试之间是一致的。
我还对一名员工进行了同样的测试,大致相同的比例成立。然而,当我将数组缩小(约1000个元素)时,当工作负载为1ms时,所做的工作量仍然大致相同。但当工作量很轻时,我得到的结果是:
- 5621311
39157931
这大约慢了7倍。
结论
- 数组越大,发生的冲突就越少,性能越好
- 工作负载(每个项目)越长,则使用保护机制的差异就越不明显
锁定似乎通常只是增加一个开销,这个开销比增加几个计数器慢2-3倍。这可能是由于实际碰撞造成的,因为(从结果来看)记录的最长锁定时间是40毫秒,但这是在工作时间非常快的时候,因此发生了许多碰撞(每次碰撞成功锁定约8次)。
这取决于访问模式,你有有效划分工作的方法吗?基本上,您可以将数组划分为8个块(或者尽可能多),并用互斥体覆盖每个部分,但如果访问模式是随机的,那么仍然会有很多冲突。
您的系统是否支持TSX?这将是一个经典的例子,只有一个全局锁,除非发生实际冲突,否则让线程忽略它。
您可以编写一个类,在特定索引需要时动态创建锁,std::optional
将对此有所帮助(C++17代码先行):
class IndexLocker {
public:
explicit IndexLocker(size_t size) : index_locks_(size) {}
std::mutex& get_lock(size_t i) {
if (std::lock_guard guard(instance_lock_); index_locks_[i] == std::nullopt) {
index_locks_[i].emplace();
}
return *index_locks_[i];
}
private:
std::vector<std::optional<std::mutex>> index_locks_;
std::mutex instance_lock_;
};
您也可以使用std::unique_ptr
来最小化堆栈空间,但保持相同的语义:
class IndexLocker {
public:
explicit IndexLocker(size_t size) : index_locks_(size) {}
std::mutex& get_lock(size_t i) {
if (std::lock_guard guard(instance_lock_); index_locks_[i] == nullptr) {
index_locks_[i] = std::make_unique<std::mutex>();
}
return *index_locks_[i];
}
private:
std::vector<std::unique_ptr<std::mutex>> index_locks_;
std::mutex instance_lock_;
};
使用此类并不一定意味着需要创建所有1000000个元素。您可以使用模运算将locker视为互斥对象的"哈希表":
constexpr size_t kLockLimit = 8;
IndexLocker index_locker(kLockLimit);
auto thread_code = [&](size_t i) {
std::lock_guard guard(index_locker.get_lock(i % kLockLimit));
// Do work with lock.
};
值得一提的是,"哈希表"方法使死锁变得非常容易(例如,get_lock(0)
后面跟着get_lock(16)
)。然而,如果每个线程一次只处理一个元素,那么这应该不是问题。
细粒度锁定还有其他权衡。原子操作是昂贵的,因此锁定每个元素的并行算法可能比顺序版本花费更长的时间。
如何有效锁定取决于。数组元素是否依赖于数组中的其他元素?你大部分时间都在读书吗?主要是写作?
我不想把数组分成8部分,因为这会导致等待的可能性很高(访问是随机的)。数组是我将编写的一个类,它将被多重Golomb编码价值观
我不认为拥有8个互斥是实现这一目标的方法。如果给定的锁保护一个数组段,那么在并行执行过程中,如果不引入竞争条件(使互斥对象变得毫无意义),就无法将其切换为保护另一个段。
数组项目小吗?如果可以将它们减少到8个字节,则可以用alignas(8)
声明类并实例化std::atomic<YourClass>
对象。(大小取决于体系结构。验证is_lock_free()
是否返回true。)这可能为无锁算法开辟了可能性。在这里,危险指示器的变体似乎很有用。这很复杂,所以如果时间有限,最好研究其他并行方法。
- Mongodb c++驱动程序:如何查询元素的数组
- 将数组作为参数传递给函数安全吗?作为第三方职能部门,可以探索他们想要的之外的其他元素
- 使用strcpy将char数组的元素复制到另一个数组
- 如何将元素添加到数组的线程安全函数?
- cpp二进制搜索问题,计算给定数组中输入元素的出现次数
- 输出没有重复元素的动态数组(收缩数组)C++
- 数组中最大的非重复元素
- 数组元素打印的递归方法
- 对字符数组中的元素执行逐位操作
- 缓存std::数组的选定元素,并在c++中自动保持其一致性
- 如何计算数组中元素的位数?(不是数组的长度),并计算其数字的总和
- 通过交换元素使数组相同
- 按平均值替换数组中的元素
- 打印矢量数组中的所有元素
- 如何将字节数组元素替换为修改的十六进制 ASCII 符号?
- 如何为 c++ 的不同变量类型的结构元素创建动态数组?
- 如何将元素从向量转移到新数组?
- 如何在向量数组中插入元素?
- 为什么 2 个相同数组的元素彼此相等
- C++ 中的二维整数数组,每行中的元素数量不均匀