CERT C: Rule CON31-C

Не уничтожайте мьютекс, пока он заблокирован

Описание

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

Не уничтожайте мьютекс, пока он заблокирован.[1]

Реализация Polyspace

Эта проверка проверяет на Уничтожение заблокированного мьютекса.

Примеры

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

Проблема

Уничтожение заблокированного мьютекса происходит, когда задача уничтожает мьютекс после его блокировки (и до его разблокировки). Блокировка и уничтожение могут происходить в одной задаче или различных задачах.

Риск

Мьютекс заблокирован, чтобы защитить общие переменные от параллельного доступа. Если мьютекс уничтожен в заблокированном состоянии, защита не применяется.

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

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

  • Инициализируйте мьютекс перед созданием потоков, где вы используете мьютекс.

  • Уничтожить мьютекс после присоединения к потокам, которые вы создали.

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

Пример - Блокировка и разрушение в различных задачах

#include <pthread.h>

pthread_mutex_t lock1;
pthread_mutex_t lock2;
pthread_mutex_t lock3;

void t0 (void) {
  pthread_mutex_lock (&lock1);
  pthread_mutex_lock (&lock2);
  pthread_mutex_lock (&lock3);
  pthread_mutex_unlock (&lock2);
  pthread_mutex_unlock (&lock1);
  pthread_mutex_unlock (&lock3);
}

void t1 (void) {
  pthread_mutex_lock (&lock1);
  pthread_mutex_lock (&lock2);
  pthread_mutex_destroy (&lock3);
  pthread_mutex_unlock (&lock2);
  pthread_mutex_unlock (&lock1);
}

В этом примере после t0 задачи блокирует мьютекс- lock3, t1 задачи может уничтожить его. Разрушение происходит, если следующие события происходят последовательно:

  1. t0 приобретает lock3.

  2. t0 выпускает lock2.

  3. t0 выпускает lock1.

  4. t1 приобретает блокировку lock1 выпущен t0.

  5. t1 приобретает блокировку lock2 выпущен t0.

  6. t1 уничтожает lock3.

Для простоты в этом примере используется смесь автоматического и ручного обнаружения параллелизма. Задачи t0 и t1 задаются вручную как точки входа при помощи опции Tasks (-entry-points). Критические разделы реализуются через примитивы pthread_mutex_lock и pthread_mutex_unlock что программное обеспечение обнаруживает автоматически. На практике для спецификации точки входа (создания резьбы) вы будете использовать примитивы, такие как pthread_create. В следующем примере показано, как дефект может появиться при использовании pthread_create.

Коррекция - Поместите пару блокировки-разблокировки вместе в том же критическом сечении, что и разрушение

Блокировка и разрушение lock3 происходит внутри критического сечения, накладываемого lock1 и lock2, но разблокировка происходит снаружи. Одной из возможных коррекций является размещение пары блокировка-разблокировка в том же критическом сечении, что и уничтожение мьютекса. Используйте один из следующих критических разделов:

  • Критический разрез, накладываемый lock1 в одиночку.

  • Критический разрез, накладываемый lock1 и lock2.

В этом исправленном коде пара блокировки-разблокировки и разрушения помещается в критический раздел, накладываемый lock1 и lock2. Когда t0 приобретает lock1 и lock2, t1 должен дождаться их релиза, прежде чем она выполнит инструкцию pthread_mutex_destroy (&lock3);. Поэтому t1 не может уничтожить mutex lock3 в заблокированном состоянии.


#include <pthread.h>

pthread_mutex_t lock1;
pthread_mutex_t lock2;
pthread_mutex_t lock3;

void t0 (void) {
  pthread_mutex_lock (&lock1);
  pthread_mutex_lock (&lock2);

  pthread_mutex_lock (&lock3);
  pthread_mutex_unlock (&lock3);

  pthread_mutex_unlock (&lock2);
  pthread_mutex_unlock (&lock1);
}

void t1 (void) {
  pthread_mutex_lock (&lock1);
  pthread_mutex_lock (&lock2);

  pthread_mutex_destroy (&lock3);

  pthread_mutex_unlock (&lock2);
  pthread_mutex_unlock (&lock1);
}

Пример - Блокировка и разрушение в стандартной программе резьбы
#include <pthread.h>

/* Define globally accessible variables and a mutex */
#define NUMTHREADS 4
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
void atomic_operation(void);

void *do_create(void *arg) {
    /* Creation thread */
    pthread_mutex_init(&lock, NULL);
    pthread_exit((void*) 0);
}

void *do_work(void *arg) {
    /* Worker thread */
    pthread_mutex_lock (&lock);
    atomic_operation();
    pthread_mutex_unlock (&lock);
    pthread_exit((void*) 0);
}

void *do_destroy(void *arg) {
    /* Destruction thread */
    pthread_mutex_destroy(&lock);
    pthread_exit((void*) 0);
}

int main (int argc, char *argv[]) {  
   int i;
   void *status;
   pthread_attr_t attr;

           
   /* Create threads */
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   /* Thread that initializes mutex */
   pthread_create(&callThd[0], &attr, do_create, NULL);

   /* Threads that use mutex for atomic operation*/
   for(i=0; i<NUMTHREADS-1; i++) {
      pthread_create(&callThd[i], &attr, do_work, (void *)i);
   }

   /* Thread that destroys mutex */
   pthread_create(&callThd[NUMTHREADS -1], &attr, do_destroy, NULL);

   pthread_attr_destroy(&attr);

   /* Join threads */
   for(i=0; i<NUMTHREADS; i++) {
      pthread_join(callThd[i], &status);
   }

   pthread_exit(NULL);
}

В этом примере создаются четыре потока. Потокам назначаются различные действия.

  • Первый поток callThd[0] инициализирует мьютекс- lock.

  • Второй и третий потоки, callThd[1] и callThd[2], выполните атомарную операцию, защищенную мьютексом lock.

  • Четвертый поток callThd[3] уничтожает мьютекс- lock.

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

Коррекция - Инициализация и уничтожение Mutex Outside Start Стандартной программы

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

#include <pthread.h>

/* Define globally accessible variables and a mutex */
#define NUMTHREADS 2
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
void atomic_operation(void);

void *do_work(void *arg) {
   pthread_mutex_lock (&lock);
   atomic_operation();
   pthread_mutex_unlock (&lock);
   pthread_exit((void*) 0);
}

int main (int argc, char *argv[]) {  
   int i;
   void *status;
   pthread_attr_t attr;

           
   /* Create threads */
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   /* Initialize mutex */
   pthread_mutex_init(&lock, NULL);

   for(i=0; i<NUMTHREADS; i++) {
      pthread_create(&callThd[i], &attr, do_work, (void *)i);
   }

   pthread_attr_destroy(&attr);

   /* Join threads */
   for(i=0; i<NUMTHREADS; i++) {
      pthread_join(callThd[i], &status);
   }

   /* Destroy mutex */
   pthread_mutex_destroy(&lock); 
  
   pthread_exit(NULL);
}

Коррекция - используйте второй мутекс, чтобы защитить пару блокировки и разблокировки и разрушения

Другой возможной коррекцией является использование второго мьютекса и защита пары блокировка-разблокировка от разрушения. Этот исправленный код использует mutex lock2 для достижения этой защиты. Второй мьютекс инициализируется в main функция вне стандартной программы потоков.

#include <pthread.h>

/* Define globally accessible variables and a mutex */
#define NUMTHREADS 4
pthread_t callThd[NUMTHREADS];
pthread_mutex_t lock;
pthread_mutex_t lock2;
void atomic_operation(void);

void *do_create(void *arg) {
    /* Creation thread */
    pthread_mutex_init(&lock, NULL);
    pthread_exit((void*) 0);
}

void *do_work(void *arg) {
    /* Worker thread */
    pthread_mutex_lock (&lock2);
    pthread_mutex_lock (&lock);
    atomic_operation();
    pthread_mutex_unlock (&lock);
    pthread_mutex_unlock (&lock2);
    pthread_exit((void*) 0);
}

void *do_destroy(void *arg) {
    /* Destruction thread */
    pthread_mutex_lock (&lock2);
    pthread_mutex_destroy(&lock);
    pthread_mutex_unlock (&lock2);
    pthread_exit((void*) 0);
}


int main (int argc, char *argv[]) {  
   int i;
   void *status;
   pthread_attr_t attr;

           
   /* Create threads */
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   /* Initialize second mutex */
   pthread_mutex_init(&lock2, NULL);

   /* Thread that initializes first mutex */
   pthread_create(&callThd[0], &attr, do_create, NULL);

   /* Threads that use first mutex for atomic operation */
   /* The threads use second mutex to protect first from destruction in locked state*/
   for(i=0; i<NUMTHREADS-1; i++) {
      pthread_create(&callThd[i], &attr, do_work, (void *)i);
   }

   /* Thread that destroys first mutex */
   /* The thread uses the second mutex to prevent destruction of locked mutex */
   pthread_create(&callThd[NUMTHREADS -1], &attr, do_destroy, NULL);


   pthread_attr_destroy(&attr);

   /* Join threads */
   for(i=0; i<NUMTHREADS; i++) {
      pthread_join(callThd[i], &status);
   }

   /* Destroy second mutex */
   pthread_mutex_destroy(&lock2);

   pthread_exit(NULL);
}

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

Группа: Правило 14. Параллелизм (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 Университет Карнеги Меллон, с специального разрешения от его Института программной инженерии.

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

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