Fortran 77多线程C ++应用程序中的常用块

时间:2015-08-25 16:24:04

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

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

我担心所有公共块可能在多个线程之间共享,并且对这些块的并发访问可能会弄乱所有内容。

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

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

4 个答案:

答案 0 :(得分:2)

你是正确的,常见的块不是线程安全的。它们是全局数据,允许您在任何共享相同存储关联的作用域单元中声明变量。如果您使用可能导致的所有线程同步问题在C ++中编写全局变量,则效果基本相同。

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

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

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

您可以探索的另一个方法是使用多处理或消息传递来并行化代码而不是多线程。这可以避免Fortran代码的线程安全问题,但会带来另一种可能代价高昂的代码体系结构变化。

另见:

答案 1 :(得分:2)

是的,共享公共块。

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

基本语法是

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

感谢您的回复者,尤其是关于OpenMP的提示,这确实是可行的。为了完全确定,我制作了一个小程序。它包含一个fortran 77部分,在一个主要的C ++程序中调用(这是我关注的):

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 ++程序 main.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 例程然后填充先前边界之间的向量。

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

我无法抓住这个节目。相反,如果我在 func.inc 中删除了fortran 77 OMP指令,那么常见的块不再是私有的,并且会出现很多错误。

总而言之,解决我的初始问题我唯一需要做的就是在任何公共块后面添加OMP指令,希望不要复杂,因为它们都聚集在一个包含文件中(比如我的测试)

希望这会有所帮助。

最好的问候。