Deadlock-prevention алгоритм
От: Lazin Россия http://evgeny-lazin.blogspot.com
Дата: 25.06.14 09:24
Оценка:
Навелосипедил небольшую бибилотеку синхронизации, с целью проверить кое что — https://github.com/Lazin/Syncope
Там есть описание на rungilsh-е, идея очень простая на самом деле. Вместо того, чтобы добавлять мьютексы как поля в объекты, можно использовать внешний массив (пулл) мьютексов. Когда мы хотим залочить объект — мы берем его адрес, вычисляем простой хэш и используем его для получения мьютекса из пулла, после чего захватываем его. Для того, чтобы это работало без дедлоков, нужно чтобы была подходящая иерархия локов в программе. Вот такой вот массив может использоваться только на одном уровне иерархии.

Выглядит это примерно вот так:
syncope::SymmetricLockLayer lock_layer(STATIC_STRING("layer name"));
...
auto guard = lock_layer.synchronize(&obj1, &obj2, ptr3);
...do anything with obj1, obj2 and ptr3


Все будет залочено до тех пор, пока область видимости переменной guard не закончится. Порядок аргументов в вызове synchronize не важен. Помимо симметричных блокировок, реализованы не симметричные.

syncope::AsymmetricLockLayer lock_layer(STATIC_STRING("layer name"));
...
{   // write access
    auto guard = lock_layer.write_lock(&obj1, &obj2, ptr3);
    ...do anything with obj1, obj2 and ptr3
}
...
{   // read access
    auto guard = lock_layer.read_lock(&obj1);
    ...read obj1
}


read блокировку можно проапгрейдить до write блокировки при желании. Моя реализация R/W Lock сильно смещена в сторону читателей и предназначена для случаев, когда запись происходить на несколько порядков реже чем чтение (например для защиты настроек программы). В этом случае read_lock работает в 2-3 раза быстрее чем pthread_rwlock_rdlock. Также возможно, что pthread_rwlock_rdlock лучше масштабируется с ростом количества ядер/процессоров, чем моя реализация.

Архитектурно, с этой штукой нужно работать следующим образом — все блокировки разделяются на уровни и для каждого уровня создается свой LockLayer (один или несколько). Каждый LockLayer — не рекурсивен, что значит, что вы не можете захватить лок под другим локом одного и того же уровня.
auto guard = lock_layer.synchronize(&obj1);
{
    // inner scope or function call under lock
    auto guard = lock_layer.synchronize(&obj2);
}

тут вы должны просто заранее захватить оба объекта, в противном случае возможны взаимные блокировки.
На практике это должно выглядеть как-то так: допустим у нас есть приложение — сервер, оно состоит из сетевой части, которая принимает запросы и новые подключения и (допустим) использует asymmetric lock layer для чтения настроек сетевого стека, white-list допустимых адресов и тд, далее запрос может обрабатываться на урвоне приложения, который использует другой lock layer для того, чтобы защитить доступ к изменяемым данным, далее, в процессе обработки прилоежние должно сохранить данные в БД, там используется еще один lock layer. Блокировки всегда захватываются в порядке, определенном иерархией — server lock layer -> application lock layer -> DB lock layer.

Теперь собственно вопрос — хочется специальный дефайн, при определении которого, включались бы проверки корректности порядка захвата и освобождения блокировок. Пока не придумал ничего лучше, нежели строить recourse allocation graph в рантайме, во время вызовов lock/unlock, а потом искать в нем циклы. Еще придумал вариант для бедных — можно сделать массив мьютексов из одного элемента, в этом случае, любой некорректное использование библиотеки будет обязательно приводить к ваимной блокировке и можно будет сравнить стектрейсы разных потоков и понять где ошибка. Может есть еще варианты?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.