Разрушение заблокированного взаимного исключения

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

Описание

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

Риск

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

Фиксация

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

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

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

На панели 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.

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

Исправление — инициализирует и уничтожает взаимное исключение внешняя стандартная программа запуска

Одно возможное исправление должно инициализировать и уничтожить взаимное исключение в функции 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++
Значение по умолчанию: 'off'
Синтаксис командной строки: DESTROY_LOCKED
Влияние: носитель
ID CWE: 667, 826

Введенный в R2017b