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