Fortran 77 общих блоков в многопоточном приложении C ++

Я разрабатываю одну программу на C ++, которая вызывает процедуру на Fortran 77. Основная программа C ++ может работать многопоточно. Однако бывает, что подпрограмма Fortran 77 скрывает несколько общих блоков, которые модифицируются при каждом вызове в зависимости от его аргументов.

Я боюсь, что все общие блоки могут быть разделены между несколькими потоками, и что одновременный доступ к этим блокам, вероятно, испортит все.

  • Первый вопрос : Я прав? Будут ли общие блоки распределены между несколькими потоками?

  • Второй вопрос : Есть ли простой способ избежать этого? Переписать процедуры на Фортране кажется невозможным, я скорее ищу способ, чтобы у каждого потока была своя копия всех общих блоков (которые не большие, их следует быстро копировать). Я не знаю, поможет ли вариант компиляции или OpenMP может мне помочь.

2

Решение

Да, общие блоки являются общими.

В OpenMP можно указать общий блок как THREADPRIVATE. Каждый поток затем динамически создает новый экземпляр общего блока. Чтобы скопировать данные из оригинала, используйте спецификатор COPYIN. Смотрите также Разница между частным OpenMP и частным

Основной синтаксис

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

где cb — это имя общего блока. Увидеть https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATE

2

Другие решения

Вы правы в том, что общие блоки не являются потокобезопасными. Это глобальные данные, которые позволяют вам объявлять переменные в любом блоке области видимости, которые имеют общую ассоциацию хранения. Эффект, по сути, тот же, если вы писали в глобальные переменные в C ++ со всеми проблемами синхронизации потоков, которые могли бы вызвать.

К сожалению, я не думаю, что есть простой способ избежать этого. Если вам нужно поддерживать многопоточный подход, одна идея, которую я видел в прошлом, заключается в том, чтобы переместить все переменные из общего блока в определенный пользователем тип и передать экземпляры этого типа в любую процедуру, требующую доступа. для них (один экземпляр на поток). Это может повлечь за собой потенциально дорогостоящие изменения в коде для реализации, хотя.

Вам также необходимо рассмотреть другие проблемы безопасности потоков в коде Фортрана (это не исчерпывающий список):

  • Модули ввода / вывода должны быть уникальными для каждого потока, иначе ввод / вывод файла не будет надежным
  • Любые переменные с SAVE Атрибут (неявный в переменных модуля и в переменных, инициализированных при объявлении) проблематичен (эти переменные постоянны между вызовами процедур). Неявность этого атрибута также зависит от компилятора / стандарта, что делает его еще более потенциальной проблемой.
  • объявить процедуры с RECURSIVE Атрибут — это означает, что функция является входной. Это также может быть выполнено путем компиляции с опцией компилятора openmp вместо изменения кода.

Другой путь, который вы могли бы изучить, — это использовать многопроцессорную обработку или передачу сообщений для параллелизации кода, а не многопоточности. Это позволяет избежать проблем безопасности потока с вашим кодом на Фортране, но представляет другое потенциально дорогое изменение архитектуры кода.

Также см:

2

Да, вы не можете использовать общие области с многопоточностью. И нет, избежать этого невозможно. Все общие области фактически объединяются компоновщиком в один блок, и нет способа скопировать его между потоками. Это известная проблема везде, где существует устаревший код Fortran. Наиболее распространенным решением является использование многопроцессорной обработки вместо многопоточности.

0

Спасибо за ваши ответы, особенно намек на OpenMP, это действительно выполнимо. Я сделал небольшую программу, чтобы быть полностью уверенным. Он состоит из одной части на Фортране 77, которая вызывается в одной основной программе на C ++ (это мое дело):

Фортран 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
endsubroutine 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) — длина одного вектора.

    Цель этой программы — разделить этот вектор между потоками.
    и позволить каждой нити заполнить одну ее часть.

    Заполнение вектора производится внутри фортрана 77:

    • задавать сначала подпрограмма устанавливает нижнюю и верхнюю границы, управляемые потоком,
    • FUNC Затем подпрограмма заполняет вектор между предыдущими границами.

Обычно, если нет ошибок и если общие блоки fortran 77 не являются общими, конечный вектор должен быть заполнен значениями 4 * k, k от 1 до 1000.

Я не мог поймать программу. И наоборот, если я удаляю директивы Fortran 77 OMP в func.inc, тогда общие блоки больше не являются частными, и возникает множество ошибок.

Итак, в заключение, единственное, что мне нужно сделать, чтобы решить мою первоначальную проблему, это добавить директивы OMP сразу за любыми общими блоками, что, надеюсь, не сложно, поскольку все они собраны в один включаемый файл (например, мой тест).

Надеется, что это помогает.

С наилучшими пожеланиями.

0
По вопросам рекламы [email protected]