exponenta event banner

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

Задача пытается уничтожить мьютекс в заблокированном состоянии

Описание

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

Риск

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

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

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

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

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

На панели Сведения о результате (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);
}

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

Группа: Параллелизм
Язык: C | C++
По умолчанию: Откл.
Синтаксис командной строки: DESTROY_LOCKED
Воздействие: среднее
CWE ID: 667, 826
Представлен в R2016b