Python действительно потокобезопасный словарь (избегание «RuntimeError: словарь изменил размер во время итерации») - PullRequest
0 голосов
/ 17 мая 2018

Я случайно получил многопоточное приложение из-за использования пакетов 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

Проблема выглядит ушел ...

Вопросы: Правильно ли я это сделал? (надеюсь, это не нарушает правило «Мы предпочитаем вопросы, на которые можно ответить, а не просто обсудить». Правило: это может ответь в любом случае).

...