ПроблемаРазрушение заблокированного мьютекса происходит, когда задача уничтожает мьютекс после того, как он заблокирован (и до того, как он разблокирован). Блокировка и разрушение могут происходить в одной и той же задаче или в разных задачах.
РискМьютекс заблокирован для защиты общих переменных от параллельного доступа. Если мьютекс уничтожен в заблокированном состоянии, защита не применяется.
ЗафиксироватьЧтобы устранить этот дефект, уничтожьте мьютекс только после его разблокировки. Это хорошая практика проектирования:
Инициализируйте мьютекс перед созданием потоков, в которых используется мьютекс.
Уничтожьте мьютекс после присоединения к созданным потокам.
На панели Сведения о результате (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 может уничтожить его. Разрушение происходит, если последовательно происходят следующие события:
t0 приобретает lock3.
t0 выпуски lock2.
t0 выпуски lock1.
t1 приобретает замок lock1 выпущено t0.
t1 приобретает замок lock2 выпущено t0.
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);
}