exponenta event banner

CERT C: POS48-C правил

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

Описание

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

Не открывайте и не уничтожайте мьютекс другого потока POSIX. [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 не может уничтожить мьютекс 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 вне процедуры запуска

Одной из возможных корректировок является инициализация и уничтожение мьютекса в 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);
}

Исправление - используйте второй мьютекс для защиты пары блокировка-разблокировка и разрушения

Другой возможной поправкой является использование второго мьютекса и защита пары блокировка-разблокировка от разрушения. Этот исправленный код использует мьютекс 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);
}

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

Группа: Правило 50. POSIX (POS)
Представлен в R2019a

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

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

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