Гонка данных

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

Описание

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

  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.
Смотрите результаты фильтра и группы.

Фиксация

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

Чтобы идентифицировать существующие меры защиты, которые можно снова использовать, см. таблицу и графики, сопоставленные с результатом. Таблица показывает каждую пару конфликтных вызовов. Столбец 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

#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;
}

В этом примере Средство поиска Ошибки обнаруживает создание, разделяют потоки pthread_create. Дефект Data race повышен, потому что операция count = count + 1 в потоке с ID thread_increment конфликтует с операцией c = count в потоке с ID thread_get. К переменной count получают доступ в нескольких потоках без общей защиты.

Две конфликтных операции являются неатомарными. Операция c = count является неатомарной на 32-битных целях. Смотрите Задают Атомарные Операции в Многозадачном Коде.

Исправление — защищает операции с парой pthread_mutex_unlock и pthread_mutex_lock

Чтобы предотвратить параллельный доступ на переменной 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
Влияние: высоко
ID CWE: 366, 413

Введенный в R2014b