Иногда нужно гарантировать эксклюзивное исполнение функции для определенного набора данных. Например, только одно одновременное изъятие данных из какого-либо источника. На помощь в таких ситуациях приходят локи.
Проще всего создать какой-нибудь lock-файл и проверять его существование. Если есть файл - lock включен.
import os
from contextlib import contextmanager
class LockedException(Exception):
    pass
@contextmanager
def file_lock(lock_file):
    """
    with file_lock('/tmp/file.lock'):
        some code here
    """
    if os.path.exists(lock_file):
        raise LockedException('LockedException')
    else:
        open(lock_file, 'w').write("1")
        try:
            yield
        finally:
            os.remove(lock_file)
Жалко напрягать HDD такими мелкими операциями. Попробуем делать lock в redis:
import redis
from contextlib import contextmanager
r = r = redis.StrictRedis(host='localhost', port=6379, db=0)
class LockedException(Exception):
    pass
@contextmanager
def redis_lock(lock_id):
    """
    with redis_lock('unique_redis_key'):
        some code here
    """
    if r.get(lock_id):
        raise LockedException('LockedException')
    else:
        r.set(lock_id, 1)
        try:
            yield
        finally:
            r.delete(lock_id)
Теперь проверим какой вариант быстрей:
if __name__ == '__main__':
    import time
    from numpy import mean
    count = 10000
    times1 = []
    for i in xrange(0, count):
        start = time.time()
        with file_lock('/tmp/file.lock'):
            pass
        times1.append(float(time.time() - start))
    times2 = []
    for i in xrange(0, count):
        start = time.time()
        with redis_lock('redis_lock'):
            pass
        times2.append(float(time.time() - start))
    print 'file lock:', max(times1), min(times1), mean(times1)
    print 'redis lock:', max(times2), min(times2), mean(times2)
Результаты:
|  | max | min | avg | 
| file_lock | 0.0817749500275 | 0.000284910202026 | 0.000996773791313 | 
| redis_lock | 0.00140619277954 | 0.000319957733154 | 0.000346991157532 | 
Lock в redis в 3 раза быстрей.
5 комментариев:
Чем не устроили семафоры https://docs.python.org/2/library/threading.html#semaphore-objects ?
Думаю что они должны быть быстее :)
А почему вообще они должны были меня устраивать? У меня все локи в данной статье перманентные и могут существовать вне контекста исполняемого процесса. Я могу залочить объект в одном скрите, 10 раз его убить, и лок останется. В случае с редисом я дополнительно к тому использую таймауты ключа, чтобы по истечению срока вероятного исполнения скрипта все-таки отпустить залоченный объект и выполнить таки действие над ним.
Я еще с учебы помню про семафоры и мьютексы, для организации блокировок. А еще я для программ на Delphi делал ограничение на запуск больше одного экземпляра с помощью семафора, т.к. другие варианты, в том числе создание временного файла, отрабатывали дольше и можно было успеть запустить два и больше экземпляра программы, пока запускался первый.
Поэтому я сразу подумал про семафоры.
И мне стало интересно чем они не устроили.
Сам я ими в python не пользовался.
Сейчас глянул, оказывается там нельзя задать идентификатор для семафора.
https://docs.python.org/2/library/fcntl.html#fcntl.lockf
Такой вариант вас тоже не устроит?
Не знаю, может сделаете тест сравнение?
Отправить комментарий