C++ 如何避免大量锁?
让C++ 如何避免大量锁?,c++,multithreading,parallel-processing,openmp,mutex,C++,Multithreading,Parallel Processing,Openmp,Mutex,让arr声明如下 std::vector<std::vector<int>> arr(10,000,000); std::vector<omp_lock_t> locks(10,000,000); 我使用openmp并定义一个锁数组,如下所示 std::vector<std::vector<int>> arr(10,000,000); std::vector<omp_lock_t> locks(10,000,000);
arr
声明如下
std::vector<std::vector<int>> arr(10,000,000);
std::vector<omp_lock_t> locks(10,000,000);
我使用openmp并定义一个锁数组,如下所示
std::vector<std::vector<int>> arr(10,000,000);
std::vector<omp_lock_t> locks(10,000,000);
这种方法在我的机器(windows linux子系统)中有效。但是我发现下面的帖子
这就引起了人们的关注:我是否使用了太多的锁,我的程序可能无法在其他平台上运行(那些限制了锁数量的平台)
我想知道是否还有另一种方法,我仍然拥有与上面相同的控制粒度,并且它没有允许的数量上限。我可以使用比较和交换之类的东西吗?根据,omp\u lock\u t
表示“一个简单的锁”。我想这是某种自旋锁。因此,您不需要关心多个互斥体的限制。(互斥体需要与内核/调度程序进行一些交互,这可能是限制其计数的原因。)
omp\u lock\t
使用单个内存位置,这对于自旋锁很好。例如,这个实时演示显示,omp\u lock\t
只需要4个字节,而std::mutex
40个字节:
请注意,自旋锁可以用一个字节实现,即使是用一个位(BTS
在x86_64上)。因此,您可以在内存中进一步压缩锁。然而,我会小心使用这种方法,因为它会带来重大的虚假共享问题
我认为你的解决方案很好。由于关键部分的操作应该非常快,而且多个线程在同一时刻访问同一元素的可能性很低,因此我认为自旋锁是一个合适的解决方案
编辑
正如评论中指出的,简单锁一词可能并不意味着锁实际上是一个自旋锁。由OpenMP实现决定将使用哪种类型的锁。然后,如果您想确保使用的是真正的自旋锁(我仍然认为它是合适的),您可以编写自己的自旋锁,或者使用提供自旋锁的库。(请注意,高效的自旋锁实现并没有那么简单。例如,它需要在加载/读取操作上自旋,而不是在交换/测试和设置上自旋,这通常是幼稚的实现所做的。)一些可能的解决方案
这真的取决于你想做什么。在最简单的场景中,您可以只使用
std::atomic_int
。在“//updatea[i]”期间您会做什么?简单一点。当只有一把锁时,你不喜欢什么?为什么不使用和?您可以决定为1024个元素的片段使用互斥锁。在Linux上,请参阅,您在//更新a[i]
中所做工作的源代码对于这个问题至关重要。对我来说,单独锁定每个int似乎是一种严重的过度使用。两个线程是否可能同时访问相同的值?OpenMP锁的实现完全取决于系统。标准中的“简单锁”仅仅意味着它不是递归(计数)锁或读写器锁。您不能假设omp\u lock\t
未使用基础系统锁实现。omp锁的大小也未指定。如果您切换到LLVM,您将看到sizeof(omp\u lock\u t)==sizeof(void*)
,因此可以在其中存储指向任意存储量的指针。“单一内存位置”也可能是锁表的索引…@JimCownie你是对的,我相应地更新了答案。我甚至检查了LLVM实现,似乎有一些条件编译可以选择锁的类型,而不仅仅是在编译时。看看KMP_LOCK_令人羡慕的那种。
// Allocate and initialise the lock array, wherever you have that!
enum { ln2NumLocks = 10,
NumLocks = 1<<ln2NumLocks }
omp_lock_t locks[NumLocks];
for (int i=0; i<NumLocks; i++)
omp_init_lock(&locks[i]);
// Hash from array index to lock index. What you have here
// probably doesn't matter too much. It doesn't need to be
// a crypto-hash!
int mapToLockIdx(int arrayIdx) {
return ((arrayIdx >> ln2NumLocks) ^ arrayIdx) & (numLocks-1);
}
// Your loop is then something like this
#pragma omp parallel for schedule(dynamic)
for (const auto [x, y] : XY) {
auto lock = &locks[mapToLock(x)];
omp_set_lock(lock);
arr[x].push_back(y);
omp_unset_lock(lock);
}
[1]: https://www.openmp.org/wp-content/uploads/SC18-BoothTalks-Cownie.pdf