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 мог запереть его, а затем освободить оба мьютекса.



2004-06-22