2

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

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

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

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

ответ

2

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

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

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

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

где центибар это имя общего блока. См. https://computing.llnl.gov/tutorials/openMP/#THREADPRIVATE

2

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

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

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

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

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

Также см:

0

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

+0

Это неправда, есть способы обойти. –

+0

@ Владымир, не могли бы вы уточнить? – SergeyA

+0

См. Мой ответ .. –

0

Спасибо за ваши ответы, особенно за подсказку об OpenMP, это действительно выполнимо. Я сделал небольшую программу, чтобы быть абсолютно уверенным. Она состоит из одной Fortran 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 
    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 * к значениям, к переходя от 1 до 1000.

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

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

Надеюсь, это поможет.

С уважением.