next up previous contents
Next: Вложенные блокировки односвязного списка. Up: Синхронизация потоков. Previous: Пример использования мьютексов.   Contents

Иерархия блокировок.

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

В этом примере, если два потока запирают мьютексы 1 и 2, то возникает тупик при попытке запереть другой мьютекс.

     Поток 1                                     Поток 2

/* использует ресурс 1 */               /* использует ресурс 2 */

pthread_mutex_lock(&m1);                pthread_mutex_lock(&m2);

/* теперь захватывает ресурсы 2         /* теперь захватывает ресурсы 1

+ 1 */                                  + 2 */

pthread_mutex_lock(&m2);                pthread_mutex_lock(&m1);

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

Если блокировка всегда выполняется в указанном порядке, тупик не возникнет. Однако, эта техника может использоваться не всегда:

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

Чтобы предотвратить тупик в этой ситуации, лучше использовать функцию
pthread_mutex_trylock(). Один из потоков должен освободить свой мьютекс, если он обнаруживает, что может возникнуть тупик.

Ниже проиллюстрирован подход условной блокировки:

Поток 1:

pthread_mutex_lock(&m1); 

pthread_mutex_lock(&m2); 

/* нет обработки */

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

Поток 2:

for (; ;) {

pthread_mutex_lock(&m2); 

if(pthread_mutex_trylock(&m1)==0) 

/* захват! */ 

break; 

/* уже заперт */ 

pthread_mutex_unlock(&m2); 

/* нет обработки */

pthread_mutex_unlock(&m1); 

pthread_mutex_unlock(&m2);

В примере выше, поток 1 запирает мьютексы в нужном порядке, а поток 2 пытается закрыть их по-своему. Чтобы убедиться, что тупик не возникнет, поток 2 должен аккуратно обращаться с мьютексом 1; если поток блокировался, ожидая мьютекс, который будет освобожден, он, вероятно, только что вызвал тупик с потоком 1. Чтобы гарантировать, что это не случится, поток 2 вызывает pthread_mutex_trylock(), который запирает мьютекс, если тот свободен. Если мьютекс уже заперт, поток 2 получает сообщение об ошибке. В этом случае поток 2 должен освободить мьютекс 2, чтобы поток 1 мог запереть его, а затем освободить оба мьютекса.



2003-12-09