【问题标题】:Fortran 77 common blocks in multithreading C++ applicationFortran 77 多线程 C++ 应用程序中的常用块
【发布时间】:2015-11-19 11:08:27
【问题描述】:

我开发了一个调用 Fortran 77 例程的 C++ 程序。主 C++ 程序可以运行多线程。但是,Fortran 77 例程会隐藏几个公共块,这些块在每次调用时都会根据其参数进行修改。

恐怕所有公共块都可能在多个线程之间共享,并且对这些块的并发访问可能会搞砸一切。

  • 第一个问题:我说的对吗?公共块会在多个线程之间共享吗?

  • 第二个问题:有没有简单的方法可以避免?重写 Fortran 例程似乎负担不起,我宁愿寻找一种方法,让每个线程都有自己的所有公共块的副本(这些块不大,应该快速复制)。我不知道编译选项是否有帮助,或者 OpenMP 是否可以帮助我。

【问题讨论】:

    标签: c++ multithreading fortran fortran77 fortran-common-block


    【解决方案1】:

    您是正确的,公共块不是线程安全的。它们是全局数据,可让您在共享相同存储关联的任何作用域单元中声明变量。如果您在 C++ 中写入全局变量,并且会导致所有线程同步问题,效果基本相同。

    不幸的是,我认为没有一种简单的方法可以避免它。如果您需要维护多线程方法,我过去看到的一个想法是将所有变量从公共块移动到用户定义的类型,并将该类型的实例传递给任何需要访问的过程给他们(每个线程一个实例)。不过,这将涉及对要实现的代码进行潜在的昂贵更改。

    您还需要查看 Fortran 代码的其他线程安全问题(这不是一个详尽的列表):

    • 每个线程的 IO 单元应该是唯一的,否则文件输入/输出将不可靠
    • 任何具有SAVE 属性的变量(隐含在模块变量和声明时初始化的变量中)都是有问题的(这些变量在过程调用之间是持久的)。该属性的隐含性还取决于编译器/标准,这使其成为更大的潜在问题。
    • RECURSIVE 属性声明过程——这意味着函数是可重入的。这也可以通过使用编译器的 openmp 选项进行编译而不是更改代码来满足。

    您可以探索的另一条路线是使用多处理或消息传递来并行化您的代码,而不是多线程。这避免了 Fortran 代码的线程安全问题,但会带来另一个可能代价高昂的代码架构更改。

    另见:

    【讨论】:

      【解决方案2】:

      是的,您不能在多线程中使用公共区域。不,没有办法避免这种情况。所有公共区域实际上都被链接器合并为单个块,并且无法在线程之间复制它。在存在遗留 Fortran 代码的任何地方,这都是一个已知问题。最常见的解决方案是使用多处理而不是多线程。

      【讨论】:

      • 这不是真的,有办法解决。
      • @VladimirF,您能详细说明一下吗?
      【解决方案3】:

      是的,公共块是共享的。

      在 OpenMP 中,可以将公共块指定为 THREADPRIVATE。每个线程都动态地创建公共块的新实例。要从原始数据复制数据,请使用 COPYIN 说明符。另见Difference between OpenMP threadprivate and private

      基本语法是

      !$OMP THREADPRIVATE (/cb/, ...)  
      

      其中 cb 是公共块的名称。见https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATE

      【讨论】:

        【解决方案4】:

        感谢您的回答,尤其是关于 OpenMP 的提示,确实可行。为了完全确定,我做了一个小程序。它由一个在一个主 C++ 程序中调用的 fortran 77 部分组成(这是我所关心的):

        fortran 77 例程func.f

          subroutine set(ii, jj)
          implicit none
        
          include "func.inc"
          integer ii, jj
          integer OMP_GET_NUM_THREADS, OMP_GET_THREAD_NUM
        
          i = ii + 1
          j = jj
        
          !$OMP CRITICAL
          print *, OMP_GET_THREAD_NUM(), OMP_GET_NUM_THREADS(), i, j
          !$OMP END CRITICAL
          return
          end
        
        
          subroutine func(n, v)
          implicit none
        
          include "func.inc"
        
          integer n, k
          integer v(n)
        
          do k = i, j
             a = k + 1
             b = a * a
             c = k - 1
             v(k) = b - c * c
          enddo
        
          return
          end
        

        包含文件func.inc

          integer i, j
          integer a, b, c
        
          common /mycom1/ i, j
          !$OMP THREADPRIVATE(/mycom1/)
          common /mycom2/ a, b, c
          !$OMP THREADPRIVATE(/mycom2/)
        

        最后是 C++ 程序 ma​​in.cpp

        #include<iostream>
        #include<sstream>
        #include<vector>
        using namespace std;
        
        #include<omp.h>
        
        extern "C"
        {
          void set_(int*, int*);
          void func_(int*, int*);
        };
        
        
        int main(int argc, char *argv[])
        {
          int nthread;
          {
            istringstream iss(argv[1]);
            iss >> nthread;
          }
        
          int n;
          {
            istringstream iss(argv[2]);
            iss >> n;
          }
        
          vector<int> a(n, -1);
        
        #pragma omp parallel num_threads(nthread) shared(a)
          {
            const int this_thread = omp_get_thread_num();
            const int num_threads = omp_get_num_threads();
        
            const int m = n / num_threads;
            int start = m * this_thread;
            int end = start + m;
        
            const int p = n % num_threads;
            for (int i = 0; i < this_thread; ++i)
              if (p > i) start++;
            for (int i = 0; i <= this_thread; ++i)
              if (p > i) end++;
        
        #pragma omp critical
            {
              cout << "#t " << this_thread << " : [" << start
                   << ", " << end << "[" << endl;
            }
        
            set_(&start, &end);
            func_(&n, a.data());
          }
        
          cout << "[ " << a[0];
          for (int i = 1; i < n; ++i)
            cout << ", " << a[i];
          cout << "]" << endl;
        
          ostringstream oss;
          for (int i = 1; i < n; ++i)
            if ((a[i] - a[i - 1]) != int(4))
              oss << i << " ";
        
          if (! oss.str().empty())
            cout << "<<!!  Error occured at index " << oss.str()
                 << " !!>>" << endl;
        
          return 0;
        }
        
        • 编译步骤(gcc 4.8.1版):

          gfortran -c func.f -fopenmp
          g++ -c main.cpp  -std=gnu++11 -fopenmp
          g++ -o test main.o func.o -lgfortran -fopenmp
          
        • 您可以按如下方式启动它:

          ./test 10 1000
          

          在哪里

          • 第一个整数 (10) 是您想要的线程数,
          • 第二个 (1000) 是一个向量的长度。

          这个程序的目的是在线程之间分割这个向量 并让每个线程填充它的一部分。

          向量的填充是在 fortran 77 中进行的:

          • 设置例程首先设置线程管理的下限和上限,
          • func 例程然后填充先前边界之间的向量。

        通常情况下,如果没有错误并且不共享 common fortran 77 块,则最终向量应填充 4 * k 值,k 从 1 到 1000。

        我无法捕捉到程序。相反,如果我删除 func.inc 中的 fortran 77 OMP 指令,那么公共块不再是私有的,并且会出现很多错误。

        因此,总而言之,解决我最初的问题需要做的唯一事情就是在任何公共块后面添加 OMP 指令,希望这不会太复杂,因为它们都收集在一个包含文件中(就像我的测试一样) .

        希望这会有所帮助。

        最好的问候。

        【讨论】:

          猜你喜欢
          • 2013-06-27
          • 1970-01-01
          • 1970-01-01
          • 2015-08-08
          • 2016-02-17
          • 2012-04-19
          • 2011-07-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多