CERT C++: CON43-C

Не разрешайте гонки данных в многопоточном коде

Описание

Определение правила

Не разрешайте гонки данных в многопоточном коде.[1]

Реализация Polyspace

Эта проверка проверяет наличие гонки данных.

Примеры

расширить все

Проблема

Гонка данных происходит, когда:

  1. Несколько задач выполняют незащищенные операции с общей переменной.

  2. По меньшей мере одна задача выполняет операцию записи.

  3. По крайней мере, одна операция неатомна. Для гонки данных как атомарных, так и неатомных операций смотрите Data race including atomic operations.

    См. «Определение атомарных операций в многозадачном коде».

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

Риск

Гонка данных может привести к непредсказуемым значениям общей переменной, потому что вы не управляете порядком операций в различных задачах.

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

 Variable value may be altered by write-write concurrent access.
См. раздел «Фильтрация и группирование результатов» в интерфейсе пользователя Polyspace Desktop.

Зафиксировать

Чтобы исправить этот дефект, защитите операции с общей переменной с помощью критических разделов, временного исключения или других средств. См. «Защита общих переменных в многозадачном коде».

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

Пример - незащищенная операция с глобальной переменной из нескольких задач


int var;
void begin_critical_section(void);
void end_critical_section(void);

void increment(void) {
    var++; 
}

void task1(void)  { 
      increment();
}

void task2(void)  { 
      increment();
}

void task3(void)  { 
     begin_critical_section();
     increment();
     end_critical_section();
}

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

ОпцияСпецификация
Configure multitasking manually
Tasks (-entry-points)

task1

task2

task3

Critical section details (-critical-section-begin -critical-section-end)Starting routineEnding routine
begin_critical_sectionend_critical_section

В командной строке можно использовать следующее:

 polyspace-bug-finder 
   -entry-points task1,task2,task3
   -critical-section-begin begin_critical_section:cs1
   -critical-section-end end_critical_section:cs1

В этом примере задачи task1, task2, и task3 вызвать функцию increment. increment содержит операцию var++ который может включать несколько машинных команд, включая:

  • Чтение var.

  • Запись повышенного значения в var.

Эти машинные инструкции, при выполнении из task1 и task2, может происходить одновременно в непредсказуемой последовательности. Для примера, чтения var от task1 может произойти либо до, либо после записи в var от task2. Поэтому значение var может быть непредсказуемым.

Хотя task3 вызывает increment внутри критического раздела другие задачи не используют тот же критический раздел. Операции в критическом разделе task3 не являются взаимоисключающими с операциями в других задачах.

Поэтому три задачи работают с общей переменной без общей защиты. В подробных данных результата вы видите каждую пару конфликтующих вызовов функций.

Если вы кликните значок, вы увидите последовательность вызова функции, начиная с точки входа и заканчивая операцией чтения или записи. Вы также видите, что операция, начиная с task3 находится в критическом разделе. Запись Access Protections показывает функцию блокировки и разблокировки, которая начинает и заканчивает критический раздел. В этом примере вы видите функции begin_critical_section и end_critical_section.

Коррекция - поместите операцию в критическое сечение

Одной из возможных коррекций является размещение операции в критическом разделе. Можно реализовать критический раздел несколькими способами. Для образца:

  • Можно разместить var++ в критическом разделе. Когда task1 входит в его критический раздел, другие задачи не могут войти в их критические разделы, пока task1 покидает его критический раздел. Область операции var++ из трех задач не могут мешать друг другу.

    Для реализации критического раздела, в функции increment, поместите операцию var++ между вызовами в begin_critical_section и end_critical_section.

    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          begin_critical_section();
          var++;
          end_critical_section(); 
    }
    
    void task1(void)  { 
          increment();
    }
    
    void task2(void)  { 
          increment();
    }
    
    void task3(void)  { 
          increment();
    }
    

  • Вы можете разместить вызов на increment в том же критическом разделе в трех задачах. Когда task1 входит в его критический раздел, другие задачи не могут войти в их критические разделы, пока task1 покидает его критический раздел. Область вызовов для increment из трех задач не могут мешать друг другу.

    Для реализации критического раздела в каждой из трех задач вызовите increment между вызовами в begin_critical_section и end_critical_section.

    
    
    int var;
    
    void begin_critical_section(void);
    void end_critical_section(void);
    
    void increment(void) {
          var++;       
    }
    
    void task1(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task2(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }
    
    void task3(void)  { 
         begin_critical_section();
         increment();
         end_critical_section();
    }

Коррекция - Делайте задачи временно эксклюзивными

Другой возможной коррекцией является выполнение задач, task1, task2 и task3, временно эксклюзивный. Временные исключающие задачи не могут выполняться одновременно.

На панели Configuration задайте следующие дополнительные опции:

ОпцияЗначение
Temporally exclusive tasks (-temporal-exclusions-file)

task1 task2 task3

В командной строке можно использовать следующее:

 polyspace-bug-finder 
     -temporal-exclusions-file "C:\exclusions_file.txt"
где файл C:\exclusions_file.txt имеет следующую линию:
task1 task2 task3

Пример - незащищенная операция в потоках, созданных с помощью pthread_create
#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;


void* increment_count(void* args)
{
    count = count + 1;
    return NULL;
}

void* set_count(void *args)
{
    long long c;
    c = count;
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);
    
    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}

В этом примере Bug Finder обнаруживает создание отдельных потоков с pthread_create. Дефект Data race возникает из-за операции count = count + 1 в нити с идентификатором thread_increment конфликтует с операцией c = count в нити с идентификатором thread_get. Переменная count доступ к нему осуществляется в нескольких потоках без общей защиты.

Две конфликтующие операции неатомны. Область операции c = count является неатомным для 32-битных целевых объектов. См. «Определение атомарных операций в многозадачном коде».

Коррекция - Защита операций с помощью pthread_mutex_lock и pthread_mutex_unlock Пара

Чтобы предотвратить параллельный доступ к переменной count, защитить операции на count с критическим сечением. Используйте функции pthread_mutex_lock и pthread_mutex_unlock для реализации критического раздела.

#include <pthread.h>

pthread_mutex_t count_mutex;
long long count;


void* increment_count(void* args)
{
    pthread_mutex_lock(&count_mutex);
    count = count + 1;
    pthread_mutex_unlock(&count_mutex);
    return NULL;        
}

void* set_count(void *args)
{
    long long c;
    pthread_mutex_lock(&count_mutex);
    c = count;
    pthread_mutex_unlock(&count_mutex);
    return NULL;
}

int main(void)
{
    pthread_t thread_increment;
    pthread_t thread_get;

    pthread_create(&thread_increment, NULL, increment_count, NULL);
    pthread_create(&thread_get, NULL, set_count, NULL);

    pthread_join(thread_get, NULL);
    pthread_join(thread_increment, NULL);

    return 1;
}

Проверяйте информацию

Группа: 10. Параллелизм (CON)
Введенный в R2019a

[1] Это программное обеспечение было создано MathWorks, включающее фрагменты: «Сайт SEI CERT-C», © 2017 Университет Карнеги Меллон, Веб-сайт SEI CERT-C + + © 2017 Университет Карнеги Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги Меллон, с специального разрешения от его Института программной инженерии.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОН И/ИЛИ ЕГО ИНЖЕНЕРНОГО ИНСТИТУТА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ НА БАЗИСЕ «КАК ЕСТЬ». УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБОГО ВОПРОСА, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИЮ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ КОММЕРЧЕСКОЙ ВЫГОДЫ, ИСКЛЮЧИТЕЛЬНОСТИ, ИЛИ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Это программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллон или его Институтом программной инженерии.