exponenta event banner

Автоматическое обнаружение создания потоков и критического сечения в полиспейсе

С помощью Polyspace ® можно анализировать программы, в которых одновременно выполняется несколько потоков. Polyspace может анализировать многозадачный код на предмет поиска данных, взаимоблокировок и других дефектов параллелизма, если анализ знает модель параллелизма в коде. В некоторых ситуациях Polyspace может автоматически обнаруживать создание потоков и критические разделы в коде. Функция поиска ошибок обнаруживает их по умолчанию. В программе Code Prover автоматическое обнаружение включается с помощью опцииEnable automatic concurrency detection for Code Prover (-enable-concurrency-detection).

Рабочий процесс анализа многозадачного кода см. в разделе Анализ многозадачных программ в многозадачном пространстве.

Если функция создания потока не обнаружена автоматически:

Многозадачные процедуры, которые может обнаружить Polyspace

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

POSIX

Создание потока: pthread_create

Начинается критический раздел: pthread_mutex_lock

Концы критического сечения: pthread_mutex_unlock

VxWorks

Создание потока: taskSpawn

Начинается критический раздел: semTake

Концы критического сечения: semGive

Для активации автоматического обнаружения примитивов параллелизма для VxWorks ® в пользовательском интерфейсе настольных продуктов Polyspace используйте VxWorks шаблон. Дополнительные сведения о шаблонах см. в разделе Создание проекта с помощью шаблона конфигурации. В командной строке используйте следующие параметры:

-D1=CPU=I80386
-D2=__GNUC__=2
-D3=__OS_VXWORKS

Обнаружение параллелизма возможно только при создании многозадачных функций из точки входа с именем main. Если точка входа имеет другое имя, например vxworks_entry_point, выполните одно из следующих действий:

  • Предоставить main функция.

  • Preprocessor definitions (-D): В определениях препроцессора, set vxworks_entry_point=main.

Windows

Создание потока: CreateThread

Начинается критический раздел: EnterCriticalSection

Концы критического сечения: LeaveCriticalSection

мкС/ОС II

Создание потока: OSTaskCreate

Начинается критический раздел: OSMutexPend

Концы критического сечения: OSMutexPost

C++ 11

Создание потока: std::thread::thread

Начинается критический раздел: std::mutex::lock

Концы критического сечения: std::mutex::unlock

Для автоматического обнаружения потоков C++ 11 явно укажите пути к файлам заголовка компилятора или используйтеpolyspace-configure.

Например, если вы используете std::thread для создания потока явно укажите путь к папке, содержащей thread.h.

См. также Ограничения автоматического обнаружения потоков.

C11

Создание потока: thrd_create

Начинается критический раздел: mtx_lock

Концы критического сечения: mtx_unlock

Пример автоматического обнаружения потоков

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

#include "pthread.h"
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t forks[5];

void* philo1(void* args)
{
    while (1) {
        printf("Philosopher 1 is thinking\n");
        sleep(1);
        pthread_mutex_lock(&forks[0]);
        printf("Philosopher 1 takes left fork\n");
        pthread_mutex_lock(&forks[1]);
        printf("Philosopher 1 takes right fork\n");
        printf("Philosopher 1 is eating\n");
        sleep(1);
        pthread_mutex_unlock(&forks[1]);
        printf("Philosopher 1 puts down right fork\n");
        pthread_mutex_unlock(&forks[0]);
        printf("Philosopher 1 puts down left fork\n");
    }
    return NULL;
}

void* philo2(void* args)
{
    while (1) {
        printf("Philosopher 2 is thinking\n");
        sleep(1);
        pthread_mutex_lock(&forks[1]);
        printf("Philosopher 2 takes left fork\n");
        pthread_mutex_lock(&forks[2]);
        printf("Philosopher 2 takes right fork\n");
        printf("Philosopher 2 is eating\n");
        sleep(1);
        pthread_mutex_unlock(&forks[2]);
        printf("Philosopher 2 puts down right fork\n");
        pthread_mutex_unlock(&forks[1]);
        printf("Philosopher 2 puts down left fork\n");
    }
    return NULL;
}

void* philo3(void* args)
{
    while (1) {
        printf("Philosopher 3 is thinking\n");
        sleep(1);
        pthread_mutex_lock(&forks[2]);
        printf("Philosopher 3 takes left fork\n");
        pthread_mutex_lock(&forks[3]);
        printf("Philosopher 3 takes right fork\n");
        printf("Philosopher 3 is eating\n");
        sleep(1);
        pthread_mutex_unlock(&forks[3]);
        printf("Philosopher 3 puts down right fork\n");
        pthread_mutex_unlock(&forks[2]);
        printf("Philosopher 3 puts down left fork\n");
    }
    return NULL;
}

void* philo4(void* args)
{
    while (1) {
        printf("Philosopher 4 is thinking\n");
        sleep(1);
        pthread_mutex_lock(&forks[3]);
        printf("Philosopher 4 takes left fork\n");
        pthread_mutex_lock(&forks[4]);
        printf("Philosopher 4 takes right fork\n");
        printf("Philosopher 4 is eating\n");
        sleep(1);
        pthread_mutex_unlock(&forks[4]);
        printf("Philosopher 4 puts down right fork\n");
        pthread_mutex_unlock(&forks[3]);
        printf("Philosopher 4 puts down left fork\n");
    }
    return NULL;
}

void* philo5(void* args)
{
    while (1) {
        printf("Philosopher 5 is thinking\n");
        sleep(1);
        pthread_mutex_lock(&forks[4]);
        printf("Philosopher 5 takes left fork\n");
        pthread_mutex_lock(&forks[0]);
        printf("Philosopher 5 takes right fork\n");
        printf("Philosopher 5 is eating\n");
        sleep(1);
        pthread_mutex_unlock(&forks[0]);
        printf("Philosopher 5 puts down right fork\n");
        pthread_mutex_unlock(&forks[4]);
        printf("Philosopher 5 puts down left fork\n");
    }
    return NULL;
}

int main(void)
{
    pthread_t ph[5];
    pthread_create(&ph[0], NULL, philo1, NULL);
    pthread_create(&ph[1], NULL, philo2, NULL);
    pthread_create(&ph[2], NULL, philo3, NULL);
    pthread_create(&ph[3], NULL, philo4, NULL);
    pthread_create(&ph[4], NULL, philo5, NULL);

    pthread_join(ph[0], NULL);
    pthread_join(ph[1], NULL);
    pthread_join(ph[2], NULL);
    pthread_join(ph[3], NULL);
    pthread_join(ph[4], NULL);
    return 1;
}

Каждому философу нужны две вилки, правая и левая. Функции philo1, philo2, philo3, philo4, и philo5 представлять философов. Для каждой функции требуется две pthread_mutex_t ресурсы, представляющие две вилки, необходимые для еды. Все пять функций выполняются одновременно в пяти параллельных потоках.

Однако в этом примере возникает взаимоблокировка. Когда каждый философ берет свою первую вилку (каждая нитка блокирует одну pthread_mutex_t ресурс), используются все вилки. Итак, философы (нитки) ждут своей второй вилки (второй pthread_mutex_t ресурс), чтобы стать доступным. Однако все развилки (ресурсы) удерживаются ожидающими философами (нитями), вызывая тупик.

Соглашение об именовании автоматически обнаруженных потоков

При использовании такой функции, как pthread_create() для создания новых потоков (задач) каждый поток связан с уникальным идентификатором. Например, в этом примере создаются два потока с идентификаторами id1 и id2.

pthread_t* id1, id2;

void main()
{
    pthread_create(id1, NULL, start_routine, NULL);
    pthread_create(id2, NULL, start_routine, NULL);
}

Если между потоками происходит расхождение данных, анализ может обнаружить его. При отображении результатов потоки обозначаются как task_id, где id - идентификатор, связанный с потоком. В предыдущем примере потоки идентифицируются как task_id1 и task_id2.

Если идентификатор потока:

  • Локальная для функции, имя потока показывает функцию.

    Например, поток, созданный ниже, отображается как task_f:id

    void f(void)
    {
        pthread_t* id;
        pthread_create(id, NULL, start_routine, NULL);
    }
  • Поле структуры, имя резьбы показывает структуру.

    Например, поток, созданный ниже, отображается как task_a#id

    struct {pthread_t* id; int x;} a;
    pthread_create(a.id,NULL,start_routine,NULL);
    
  • Элемент массива, имя потока показывает массив.

    Например, поток, созданный ниже, отображается как task_tab[1].

    pthread_t* tab[10];
    pthread_create(tab[1],NULL,start_routine,NULL);
    

Если создаются два потока с различными идентификаторами потоков, но для идентификаторов потоков используется одно и то же имя локальной переменной, имя второго потока изменяется, чтобы отличить его от первого потока. Например, перечисленные ниже потоки отображаются как task_func:id и task_func:id:1.

void func()
{
    {
        pthread_t id;
        pthread_create(&id, NULL, &task, NULL);

    }
    {
        pthread_t id;
        pthread_create(&id, NULL, &task, NULL);

    }
}

Ограничения автоматического обнаружения потоков

Многозадачная модель, извлеченная Polyspace, не включает некоторые функции. Полиспейс не может моделировать:

  • Приоритеты и атрибуты потоков - игнорируются Polyspace.

  • Рекурсивные семафоры.

  • Неограниченные идентификаторы потоков, такие как extern pthread_t ids[] - Предупреждение.

  • Вызовы примитива параллелизма через вызовы высокого порядка - предупреждение.

  • Псевдонимы на идентификаторах потоков (Aliases on thread identifiers) - сверхаппроксимация полипространства при использовании псевдонима.

  • Завершение потоков - Polyspace игнорирует pthread_join и thrd_join. Polyspace заменяет pthread_exit и thrd_exit стандартным выходом.

  • (Только ошибка Polyspace Finder™) Создание нескольких потоков с помощью нескольких вызовов одной и той же функции с разными аргументами указателя.

     Пример

  • (Только Prover™ кода Polyspace) Общие локальные переменные - общими считаются только глобальные переменные. Если доступ к локальной переменной осуществляется несколькими потоками, анализ не учитывает общий характер переменной.

     Пример

  • (Только средство проверки кода Polyspace) Общая динамическая память - общими считаются только глобальные переменные. Если динамически выделенная область памяти доступна нескольким потокам, анализ не учитывает ее общий характер.

     Пример

  • Количество задач, созданных с помощью CreateThread когда threadId имеет значение NULL- При создании нескольких потоков, выполняющих одну и ту же функцию, если последний аргумент CreateThread является NULL, Polyspace обнаруживает только один экземпляр этой функции или задачи.

     Пример

  • (Только C++ 11) Если лямбда-выражения используются в качестве функций запуска при создании потока, Polyspace не обнаруживает общие переменные в лямбда-выражениях.

     Пример

  • (Только потоки C++ 11 с проверкой кода Polyspace) Строковые литералы в качестве аргумента функции потока - Функция проверки кода показывает ошибку указателя «Незаконно» красного цвета, если функция потока имеет std::string& и передается строковый литеральный аргумент.

     Пример

См. также

|

Связанные темы