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实现决定将使用哪种类型的锁。然后,如果您想确保使用的是真正的自旋锁(我仍然认为它是合适的),您可以编写自己的自旋锁,或者使用提供自旋锁的库。(请注意,高效的自旋锁实现并没有那么简单。例如,它需要在加载/读取操作上自旋,而不是在交换/测试和设置上自旋,这通常是幼稚的实现所做的。)

一些可能的解决方案

  • 如果您使用的是支持事务同步扩展(TSX)的现代英特尔处理器,则可以使用单个推测锁。(请参阅[您可能错过的两个小型OpenMP功能][1])。这显示了一个非常相似的用例

  • 您可以使用较小的锁数组,并使用哈希将数组索引映射到锁集中。 类似这样的东西(未经测试且未编译!)


    这真的取决于你想做什么。在最简单的场景中,您可以只使用
    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