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