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