多线程c++应用程序中的Fortran 77通用块
Fortran 77 common blocks in multithreading C++ application
我开发了一个调用Fortran 77例程的c++程序。主c++程序可以多线程运行。然而,碰巧Fortran 77例程隐藏了几个公共块,这些块在每次调用时根据其参数进行修改。
我担心所有的公共块可能在多个线程之间共享,并发访问这些块可能会把一切搞砸。
-
第一个问题:我说的对吗?公共块会在多个线程之间共享吗?
-
第二个问题:是否有一个简单的方法来避免它?重写Fortran例程似乎负担不起,我正在寻找一种方法,以便每个线程都有自己的所有公共块的副本(这些块并不大,应该可以快速复制)。我不知道编译选项是否会有帮助,或者OpenMP是否可以帮助我。
是的,公共块是共享的。
在OpenMP中,可以将公共块指定为THREADPRIVATE。每个线程动态创建一个公共块的新实例。要从原始数据中复制数据,请使用COPYIN说明符。参见OpenMP threadprivate和private的区别 基本语法是!$OMP THREADPRIVATE (/cb/, ...)
,其中cb是公共块的名称。参见https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATE
你是正确的,普通块不是线程安全的。它们是全局数据,允许您在任何范围单元中声明所有共享相同存储关联的变量。如果你在c++中写全局变量,结果基本上是一样的,因为所有的线程同步问题都会导致。
不幸的是,我认为没有一个简单的方法来避免它。如果您需要维护多线程方法,我过去看到的一个想法是将所有变量从公共块移动到用户定义的类型中,并将该类型的实例传递给需要访问它们的任何过程(每个线程一个实例)。但是,这可能涉及到对代码进行昂贵的修改。
您还需要查看Fortran代码的其他线程安全问题(这不是一个详尽的列表):
- IO单元应该是每个线程唯一的,否则文件输入/输出将不可靠
- 任何带有
SAVE
属性的变量(在模块变量和声明时初始化的变量中隐式)都是有问题的(这些变量在过程调用之间是持久的)。这个属性的隐式性也依赖于编译器/标准,这使得它成为一个更大的潜在问题。 - 声明带有
RECURSIVE
属性的过程——这意味着函数是可重入的。这也可以通过编译器的openmp选项来满足,而不是修改代码。
您可以探索的另一种方法是使用多处理或消息传递来并行化代码,而不是使用多线程。这避免了Fortran代码的线程安全问题,但提出了另一个可能代价高昂的代码体系结构更改。
也看到:
- https://software.intel.com/en-us/forums/topic/276535
- https://software.intel.com/en-us/forums/topic/270572
是的,你不能使用多线程的公共区域。不,这是无法避免的。所有公共区域实际上都被链接器合并到一个块中,并且没有办法在线程之间复制它。在遗留Fortran代码存在的任何地方,这都是一个众所周知的问题。最常见的解决方案是使用多处理而不是多线程。
感谢您的回答,特别是关于OpenMP的提示,它确实是可行的。为了完全确定,我编写了一个小程序。它由一个fortran 77部分组成,在一个主要的c++程序中调用(这是我关心的):
fortran 77例程函数。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
与include file 函数。公司
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:
- set例程首先设置线程管理的下限和上限,
- func例程然后填充前面边界之间的向量。
通常情况下,如果没有错误并且没有共享公共fortran 77块,则最终向量应该填充4 * k值,k从1到1000。
我抓不到程序。相反,如果我在函数中删除fortran 77 OMP指令。. inc,那么公共块不再是私有的,并且会出现许多错误。
所以总结,我需要做的唯一一件事来解决我的初始问题是添加OMP指令后面的任何公共块,希望不是太复杂,因为它们都聚集在一个包含文件(像我的测试)。
希望这对你有帮助。
问好。
- 表示"accepting anything for this template argument" C++概念的通配符
- 当使用通配符和null指针调用函数时,对输出的说明
- 赛通"Cannot take address of memoryview slice"
- C++17 文件系统::remove_all 带有通配符路径
- 使用 VS2015 在 Windows 10 上构建 Fortran .lib x64 位并将其链接到 C++
- 如何通过多类"Union variable" (sfml) 使用轮询事件
- 通用线程池类工作不正常
- 将 OR 逻辑运算符从 C++ 转换为 Fortran
- 如何允许通配符模板参数
- 使用通配符的跨平台文件列表
- test1.cpp:9:77:错误:对"(const std::normal_distribution) <double>(std::mt19937&)"的调用不匹配
- 如何使用 std::vector 作为参数调用 Fortran 77 C++函数?
- 我如何向只用Fortran 77编码的人解释面向对象编程?
- 使用Fortran 77子程序作为独立程序,从C++调用
- Fortran 77到C++的翻译——它的工作原理
- C++如何称呼Fortran 77的公共块
- Make可以找到FORTRAN 77文件,但不能找到FORTRAN 90文件
- 在fortran 77中使用c++类对象
- 多线程c++应用程序中的Fortran 77通用块
- Fortran 77处理c++内存分配