Поддержка многопоточности в пользовательском приложении

Чтобы использовать интерфейсы ядра в нескольких потоках, для ядра должен быть установлен режим многопоточных вычислений не ниже mtm_SafeItems. По умолчанию в ядре установлен максимальный режим многопоточности mtm_Max.

Если пользовательское приложение использует интерфейсы ядра в нескольких потоках, оно обязано сообщать ядру о входе в каждый параллельный регион и о выходе из него. Для этого могут быть использованы класс ParallelRegionGuard (защитник параллельного региона в области видимости) или парные функции EnterParallelRegion и ExitParallelRegion.

Защита параллельного кода в пользовательском приложении

Класс ParallelRegionGuard сообщает ядру, что код в области видимости выполняется параллельно. Он должен использоваться для защиты параллельного кода.

Вместо класса ParallelRegionGuard следующая пара функций также может быть использована для нотификации ядра о границах параллельного региона кода:

  • Функция EnterParallelRegion сообщает ядру о входе в параллельный блок кода.

  • Функция ExitParallelRegion сообщает ядру о выходе из параллельного блока кода.

Использование ParallelRegionGuard или функций EnterParallelRegion и ExitParallelRegion является обязательным, если интерфейсы ядра используются в нескольких потоках.

Прежде чем начать использовать параллельные вычисления, нужно убедиться, что в ядре установлен режим многопоточности не ниже mtm_SafeItems (по умолчанию в ядре установлен максимальный режим многопоточности mtm_Max).

Примеры уведомления ядра о его использовании в параллельных вычислениях

ParallelRegionGuard должен вызываться один раз перед началом работы в нескольких потоках. Например:

// ... Последовательный код ...

if( Math::CheckMultithreadedMode( mtm_Items ) ) {
  ParallelRegionGuard guard; // Работает в области видимости

  std::thread t1( func1 );
  std::thread t2( func2 );

  t1.join();
  t2.join();
}
// ... Последовательный код ...

То же самое можно сделать, используя пару функций EnterParallelRegion() и ExitParallelRegion() вместо ParallelRegionGuard:

// ... Последовательный код ...

if( Math::CheckMultithreadedMode( mtm_Items ) ) {
  EnterParallelRegion();

  std::thread t1( func1 );
  std::thread t2( func2 );

  t1.join();
  t2.join();

  ExitParallelRegion();
}

// ... Последовательный код ...

Интерфейсы ParallelRegionGuard, EnterParallelRegion и ExitParallelRegion должны использоваться, чтобы нотифицировать ядро о распараллеливании в пользовательском приложении. Эти нотификации нужны, чтобы обеспечить правильную работу механизмов потокобезопасности ядра.

Краткая справка по организации параллельных вычислений с использованием интерфейсов ядра C3D

  1. Перед запуском параллельных вычислений приложение должно проверить, что установленный режим многопоточности ядра позволяет использовать параллельные вычисления. Чтобы использовать интерфейсы ядра в нескольких потоках, в ядре должен быть установлен режим многопоточных вычислений не ниже mtm_SafeItems.

    Пример проверки режима ядра:

    bool useParallel = Math::CheckMultithreadedMode( mtm_SafeItems );

  2. При использовании параллельных вычислений пользовательское приложение обязано нотифицировать ядро о входе в параллельный регион и выходе из него.

    Если используется параллельный цикл, то перед его началом нужно вызвать EnterParallelRegion, а после окончания – ExitParallelRegion.

    Пример:

    if( Math::CheckMultithreadedMode( mtm_Items ) ) {
      EnterParallelRegion();
    
      #pragma omp parallel for
      for( ptrdiff_t i = 0; i < count; ++i ) {
        // Тело цикла
      }
    
      ExitParallelRegion();
    }
    

    Если интерфейсы ядра просто вызываются из разных потоков, то в каждом потоке перед вызовами интерфейсов ядра должен быть вызов EnterParallelRegion, а после окончания использования ядра – вызов ExitParallelRegion.

    Пример:

    // Поток 1:
    EnterParallelRegion();
    // Вызовы интерфейсов ядра
    ExitParallelRegion();
    
    // Поток 2:
    EnterParallelRegion();
    // Вызовы интерфейсов ядра
    ExitParallelRegion();
    

    После первого вызова EnterParallelRegion ядро начнет использовать многопоточные кэши, а после последнего вызова ExitParallelRegion вернется в базовый режим одного потока.

  3. Не рекомендуется вызывать функции EnterParallelRegion и ExitParallelRegion без необходимости (в отсутствие многопоточности), так как использование многопоточных кэшей влечет за собой дополнительные затраты времени на получение кэшированных данных.