﻿# OpenMP и исключения \(exceptions\)

Мы продолжаем развивать наш статический анализатор VivaMP, и на этот раз хочется рассказать о диагностике ошибок, связанных с использованием исключений \(exception\) языка Си\+\+ в параллельных регионах \(parallel regions\)\.


> Поддержка VivaMP была прекращена в 2014 году\\\. По всем возникшим вопросам вы можете обратиться в нашу \[поддержку\]\(https://pvs\-studio\.ru/ru/about\-feedback/\)\\\.

Под параллельным регионом имеются виду фрагмент программы, который делится на параллельно выполняемые нити\. Параллельно выполняемые нити формируют такими директивами OpenMP как for и sections\.Использовать исключения внутри параллельных регионов можно\. Но исключения не должны покидать эти параллельные регионы\. Исключения должны быть пойманы и обработаны внутри параллельного региона с использованием конструкций try/catch\. Если исключение выйдет за приделы параллельного региона, то это приведет к сбою и скорее всего к аварийному завершению программы\. Рассмотрим пример некорректного кода:

```cpp
#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
  //...
  throw 1;
}
```

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

```cpp
size_t errCount = 0;
#pragma omp parallel for num_threads(4) reduction(+: errCount)
for(int i = 0; i < 4; i++)
{
  try {
    //...
    throw 1;
  }
  catch (...)
  {
    ++errCount;
  }
}
if (errCount != 0)
  throw 1;
```

Выглядит несколько сложно, но если вам необходимо использовать исключения внутри параллельных регионов то другого выхода нет, и вам придется создать подобный механизм\. Но лучше конечно постараться обойтись без исключений\.

Дополнительная сложность состоит в том, что исключение может вызвать не только ваш код, в котором вы напишите throw\. Исключения могут быть сгенерированы и в используемых вами функциях или операторах выделения памяти\. Рассмотрим следующий, на первый взгляд безобидный пример:

```cpp
#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
  float *ptr = new float[10000];
  delete [] ptr;
}
```

Такой код может годами надежно работать, а может привести к аварийному завершению программы, если в какой\-то момент оператор 'new' не сможет выделить объем необходимой памяти\. Согласно стандарту языка Си\+\+ оператор new выбрасывает исключение std::bad\_alloc если не может выделить необходимый объем памяти\. Такой подход позволяет не проверять, выделена ли необходимая память, как это делается в случае использования функции malloc, а сразу начать работу с ней\. Если же память не выделена, то программа обработает эту ситуацию в нужном месте\. В случае с параллельным регионом необходима дополнительная работа, чтобы корректно обработать ошибку выделения памяти внутри самого региона\. Исправленный пример:

```cpp
#pragma omp parallel for num_threads(4) reduction(+: errCount)
for(int i = 0; i < 4; i++)
{
  try {
    float *ptr = new float[10000];
    delete [] ptr;
  }
  catch (std::bad_alloc &)
  {
    //process error
  }
}
```

Я думаю, что вы уже догадались, что использование функций внутри параллельных секций дело тоже опасное и неблагодарное\. Необходимо или быть уверенным, что функции не генерируют исключений, или обертывать их в try/catch\. Неприятный момент в том, что если на момент написания параллельного кода, используемые в нем функции не генерировали исключение, то со временем это может измениться и нужно быть очень аккуратным\.

Подводя итог, можно сказать, что исключения являются моментом, о котором нужно постоянно помнить, разрабатывая программу с использованием OpenMP\. Чтобы упростить жизнь программистов, мы добавили в анализатор VivaMP три новых диагностических сообщения, которые помогут отловить ошибки связанные с использованием исключений:

V1301\. The 'throw' keyword cannot be used outside of a try\.\.catch block in a parallel section

V1302\. The 'new' operator cannot be used outside of a try\.\.catch block in a parallel section\.

V1303\. The FOO function which throws an exception cannot be used in a parallel section outside of a try\.\.catch block\.

Диагностическое сообщение V1301 укажет на ошибку в первом примере, а V1302 диагностирует ошибки вызова оператора 'new' вне обработчика исключений\. С V1303 все немного сложнее\. Сейчас анализатор VivaMP будет предупреждать только о вызове функций, явно помеченных как бросающих исключения, то есть:

```cpp
void MyThrowFoo() throw(...) { }
```

Функции не отмеченные "throw\(\.\.\.\)" тоже могут бросать исключения, но они не диагностируются как опасные\. Этот шаг сделан сознательно, чтобы уменьшить количество лишних диагностических сообщений\. Ведь получится, что любой вызов функции в параллельной секции, неэкранированный конструкцией try/catch будет опасным\. Хотя именно так оно и есть, но польза от такого количества диагностических сообщений сомнительна\. Но возможно именно так себя в дальнейшем будет вести себя анализатор VivaMP в режиме "pedantic mode"\. И тогда безопасными, буду считать функции, явно помеченные, как не выбрасывающие исключений:

```cpp
void MyNotThrowFoo() throw() { }
```

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

```cpp
#pragma omp parallel num_threads (4)
{
    try {
        if (omp_get_thread_num ()  ==  0) {
            throw CException();
        }
        #pragma omp barrier
    }
    catch(CException &) {
    }
}
```

После генерации исключения один из потоков "пропустит" директиву barrier, в результате чего возникнет зависание\. И остальные потоки будут вечно ждать поток, в котором произошло исключение\. Возможно, в следующей версии анализатора будет добавлен поиск соответствующих ошибок, но пока с этим связаны определенные технические сложности\.

Дополнительная литература

1. Обработка исключений в C\+\+\. [http://www\.codenet\.ru/progr/cpp/Try\-Catch\-Throw\.php](http://www.codenet.ru/progr/cpp/Try-Catch-Throw.php)
1. Wikipedia\. [Обработка исключений](https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B9)\.
1. Blog\. Michael Suess\. Exceptions in OpenMP and C\+\+ \- what's the state of affairs today? [http://www\.thinkingparallel\.com/2006/10/07/exceptions\-in\-openmp\-and\-c\-whats\-the\-state\-of\-affairs\-today/](http://www.thinkingparallel.com/2006/10/07/exceptions-in-openmp-and-c-whats-the-state-of-affairs-today/)
1. Blog\. Michael Suess\. Making Exceptions Work with OpenMP \- Some Tiny Workarounds\. [http://www\.thinkingparallel\.com/2006/11/30/making\-exceptions\-work\-with\-openmp\-some\-tiny\-workarounds/](http://www.thinkingparallel.com/2006/11/30/making-exceptions-work-with-openmp-some-tiny-workarounds/)
1. Blog\. Michael Suess\. Breaking Out of Loops in OpenMP\. [http://www\.thinkingparallel\.com/2007/06/29/breaking\-out\-of\-loops\-in\-openmp/](http://www.thinkingparallel.com/2007/06/29/breaking-out-of-loops-in-openmp/)