Иногда нужно гарантировать эксклюзивное исполнение функции для определенного набора данных. Например, только одно одновременное изъятие данных из какого-либо источника. На помощь в таких ситуациях приходят локи.
Проще всего создать какой-нибудь 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
Такой вариант вас тоже не устроит?
Не знаю, может сделаете тест сравнение?
Отправить комментарий