exponenta event banner

Гонка данных

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

Описание

Этот дефект возникает в следующих случаях:

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

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

  3. По меньшей мере одна операция является неатомной. Информацию о гонке данных по атомным и неатомным операциям см. в разделе Data race including atomic operations.

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

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

Риск

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

Гонки данных между двумя операциями записи более серьезны, чем гонки данных между операцией записи и операцией чтения. Две операции записи могут создавать помехи друг другу и приводить к неопределенным значениям. Чтобы определить конфликты записи, используйте фильтры в столбце Подробности (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)Запуск подпрограммыЗавершение процедуры
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

#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. Дефект гонки данных возникает из-за операции 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;
}

Информация о результатах

Группа: Параллелизм
Язык: C | C++
По умолчанию: Вкл.
Синтаксис командной строки: DATA_RACE
Воздействие: Высокое
CWE ID: 366, 413
Представлен в R2014b