Я случайно получил многопоточное приложение из-за использования пакетов web.py
и pyinotify
:)
Приложение сохраняет состояние сортировки в файлах JSON, которое оно может изменить и которое может изменить кто-либо другой. Измененный JSON перезагружается обратно в dict
потоком inotify
наблюдателя.
Приложение запускает десятки потоков в довольно простых условиях.
Ну наконец-то я наблюдаю
RuntimeError: dictionary changed size during iteration.
Я понял, мне нужна блокировка вокруг каждой операции словаря. В этом ( этих на самом деле) словаре, я имею в виду.
Я пытался обойти это совсем по-другому (да, копая в основном стек-поток).
Я столкнулся с тупиковой ситуацией в случае невозвратных блокировок (это кажется мне очевидным сейчас ).
Я закончил с таким подходом:
DictLock = threading.RLock # use reentrant locks here!
class ThreadSafeDict(dict):
'''It must avoid things like
RuntimeError: dictionary changed size during iteration.
Inspired by https://stackoverflow.com/questions/13456735
and https://stackoverflow.com/questions/3278077
'''
def __init__(self, *av, **kw):
self._lock = kw.pop('_lock') if '_lock' in kw else DictLock()
super(ThreadSafeDict, self).__init__(*av, **kw)
self._thread = None
# do NO LOCKS here!
def replace(self, dictionary):
super(ThreadSafeDict, self).clear()
super(ThreadSafeDict, self).update(dictionary)
return self
def load_json(self, filename):
with open(filename) as fp:
super(ThreadSafeDict, self).clear()
super(ThreadSafeDict, self).update(json.load(fp))
return self
def save_json(self, filename, indent=2):
with open(filename, 'w') as fp:
json.dump(self, fp, indent=indent)
return self
# all locking is being performed below
def __getattribute__(self, name):
if name == '_lock':
return super(ThreadSafeDict, self).__getattribute__(name)
with self._lock:
self._thread = threading.current_thread()
item = super(ThreadSafeDict, self).__getattribute__(name)
if not callable(item):
log.debug('[%r]=>%r', name, item)
return item # return property value as is
def wrapper(*a, **k):
with self._lock:
return item(*a, **k)
log.debug('%r()=>{%r}', name, item.__name__)
return wrapper # return locked calls for methods
#end class ThreadSafeDict
Проблема выглядит ушел ...
Вопросы: Правильно ли я это сделал? (надеюсь, это не нарушает правило «Мы предпочитаем вопросы, на которые можно ответить, а не просто обсудить». Правило: это может ответь в любом случае).