【问题标题】:c++ OpenMP critical: "one-way" locking?c ++ OpenMP关键:“单向”锁定?
【发布时间】:2023-03-11 05:56:01
【问题描述】:

考虑以下串行函数。当我并行化我的代码时,每个线程都会从并行区域内调用这个函数(未显示)。我正在尝试使这个线程安全高效(快速)。

float get_stored_value__or__calculate_if_does_not_yet_exist( int A )
{    
    static std::map<int, float> my_map;

    std::map::iterator it_find = my_map.find(A);  //many threads do this often.

    bool found_A =   it_find != my_map.end();

    if (found_A)
    {
        return it_find->second;
    } 
    else
    {
      float result_for_A = calculate_value(A);  //should only be done once, really.
      my_map[A] = result_for_A;
      return result_for_A;
    }    
}

几乎每次调用此函数时,线程都会成功“找到”其“A”的存储值(无论它是什么)。每隔一段时间,当调用“新 A”时,就必须计算和存储一个值。

那么我应该把#pragma omp critical 放在哪里?

虽然很简单,但在所有这些内容周围放置一个#pragma omp critical非常低效,因为每个线程都会不断地执行此操作,并且通常是只读的情况。

有没有办法实现“单向”critical 或“单向”lock 例程?也就是说,上述涉及迭代器的操作应该只有在else语句中写入my_map时才应该被“锁定”。但是多个线程应该能够同时执行.find调用。

我希望我说得通。 谢谢。

【问题讨论】:

    标签: c++ parallel-processing critical-section openmp


    【解决方案1】:

    根据this link on Stack Overflow 插入std::map 不会使迭代器无效。 end() 迭代器也是如此。 Here's a supporting link.

    很遗憾,如果您不使用关键部分,插入可能会发生多次。此外,由于您的 calculate_value 例程可能在计算上很昂贵,因此您必须锁定以避免此 else 子句使用相同的 A 值操作两次,然后插入两次。

    这是一个示例函数,您可以在其中复制这种不正确的多次插入:

    void testFunc(std::map<int,float> &theMap, int i)
    {
        std::map<int,float>::iterator ite = theMap.find(i);
    
        if(ite == theMap.end())
        {
             theMap[i] = 3.14 * i * i;
         }
    }
    

    然后这样调用:

    std::map<int,float> myMap;
    
    int i;
    #pragma omp parallel for
    for(i=1;i<=100000;++i)
    {
        testFunc(myMap,i % 100);
    }
    
    if(myMap.size() != 100)
    {
        std::cout << "Problem!" << std::endl;
    }
    

    编辑:编辑以纠正早期版本中的错误。

    【讨论】:

    • 即使.insert 出现“中间查找”,这是否正确? (即当这个线程在 .find 调用中时)。
    • 虽然插入操作可能会被调用多次,但实际写入可能会发生一次或多次,具体取决于您使用的插入操作符。使用operator[],例如myMap[i] = 3.14*i*i 将导致多次写入。但是,myMap.insert(std::pair&lt;int, float&gt;(i, 3.14*i*i)) 实际上只会写一次。
    【解决方案2】:

    OpenMP 是用于自动循环并行化的编译器“工具”,而不是线程通信或同步库;因此它没有复杂的互斥锁,例如读/写互斥锁:在写入时获取锁,但在读取时获取锁。

    这是implementation example

    无论如何,Chris A. 的回答比我的要好 :)

    【讨论】:

      【解决方案3】:

      虽然@ChrisA 的回答可能会解决您的问题,但我会在此处留下我的回答,以防将来的搜索者发现它有用。

      如果您愿意,可以给#pragma omp critical 部分提供name。然后,具有该名称的任何部分都被视为相同的关键部分。如果这是您想要做的,您可以轻松地使您的方法的一小部分变得至关重要。

      #pragma omp critical map_protect
      {
          std::map::iterator it_find = my_map.find(A);  //many threads do this often.
      
          bool found_A =   it_find != my_map.end();
      }
      

      ...

      #pragma omp critical map_protect
      {
          float result_for_A = calculate_value(A);  //should only be done once, really.
          my_map[A] = result_for_A;
      }
      

      #pragma omp atomic#pragma omp flush 指令也可能有用。

      atomic 导致对内存位置(指令前面的表达式中的左值)的写入始终是原子的。

      flush 确保预期对所有线程可用的任何内存实际上都写入所有线程,而不是存储在处理器缓存中,并且在它应该在的地方不可用。

      【讨论】:

      • 但是你对critical的使用是我担心的低效率 - 由于critical,多个线程无法同时读取地图。
      • atomic 是个好主意。那么我只需要在写入区域周围放置一个atomicflush 吗? - 并且读取区域需要指令?
      • @CycoMatto 我知道。我以为我用atomicflush 做了一些聪明的事情,意识到我的错误,并编辑了我的答案。它提供的信息可能对偶然发现您的问题的其他人有用,但绝对不能提供您想要的锁定行为。
      • @CycoMatto atomic 只保护一个语句,而不是一个块,这就是我的聪明才智失败的原因。 flush 将放在任何尝试读取之前 (.find)。
      猜你喜欢
      • 2013-06-05
      • 2016-05-28
      • 2021-11-18
      • 2015-01-25
      • 2014-04-20
      • 2013-10-23
      • 1970-01-01
      • 2020-02-02
      • 2014-03-02
      相关资源
      最近更新 更多