кэш объектов Python threadsafe



Я реализовал веб-сервер python. Каждый http-запрос порождает новый поток. У меня есть требование кэширования объектов в памяти, и поскольку это веб-сервер, я хочу, чтобы Кэш был потокобезопасным. Существует ли стандартная реализация кэша потокобезопасных объектов в python? Я нашел следующее

Http://freshmeat.net/projects/lrucache/

Это не выглядит потокобезопасным. Может ли кто-нибудь указать мне на хорошую реализацию потокобезопасного кэша в в Python?

Спасибо!

134   5  

5 ответов:

Ну, многие операции в Python по умолчанию потокобезопасны, поэтому стандартный словарь должен быть в порядке (по крайней мере, в некоторых отношениях). Это в основном связано с GIL, который поможет избежать некоторых более серьезных проблем с потоками.

Вот список: http://coreygoldberg.blogspot.com/2008/09/python-thread-synchronization-and.html это может оказаться полезным.

Хотя атомарная природа этих операций просто означает, что у вас не будет полностью несогласованного состояния, если у вас есть два потока, обращающихся к словарю одновременно. Так что у вас не будет испорченной ценности. Однако вы (как и большинство многопоточных программ) не сможете полагаться на конкретный порядок этих атомарных операций.

Короче говоря...

Если у вас достаточно простые требования и вы не беспокоитесь о порядке того, что записывается в кэш, то вы можете использовать словарь и знать, что вы всегда получите согласованный / не поврежденный значение (оно просто может быть устаревшим).

Если вы хотите убедиться, что вещи немного более последовательны в отношении чтения и записи, то вы можете посмотреть на локальный кэш памяти Django:

Http://code.djangoproject.com/browser/django/trunk/django/core/cache/backends/locmem.py

, который использует блокировку чтения / записи для блокировки.

Поток на запрос часто является плохой идеей. Если ваш сервер испытывает огромные скачки нагрузки, он поставит коробку на колени. Рассмотрите возможность использования пула потоков, который может увеличиваться до ограниченного размера во время пикового использования и уменьшаться до меньшего размера, когда нагрузка невелика.

Вы, вероятно, хотите использовать memcached вместо этого. Он очень быстрый, очень стабильный, очень популярный, имеет хорошие библиотеки python и позволит вам расти до распределенного кэша, если вам нужно:

Http://www.danga.com/memcached/

Для потокобезопасного объекта требуется потокобезопасность.местный:

from threading import local

safe = local()

safe.cache = {}

Затем вы можете поместить и извлечь объекты в safe.cache с потокобезопасностью.

Пункт 1. GIL здесь вам не поможет, примером (не потокобезопасного) кэша для чего-то под названием "заглушки" будет

stubs = {}

def maybe_new_stub(host):
    """ returns stub from cache and populates the stubs cache if new is created """
    if host not in stubs:
        stub = create_new_stub_for_host(host)
        stubs[host] = stub
    return stubs[host]

Что может произойти, так это то, что поток 1 вызывает maybe_new_stub('localhost'), и он обнаруживает, что у нас еще нет этого ключа в кэше. Теперь мы переключаемся на поток 2, который вызывает тот же maybe_new_stub('localhost'), и он также узнает, что ключа нет. Следовательно, оба потока вызывают create_new_stub_for_host и помещают его в кэш.

Сама карта защищена GIL, поэтому мы не можем разбить ее с помощью параллельный доступ. Логика тайника, однако, не защищена, и поэтому мы можем в конечном итоге создать два или более заглушек и бросить все, кроме одного на пол.

Пункт 2. в зависимости от характера программы Глобальный кэш может не понадобиться. Такой общий кэш обеспечивает синхронизацию между всеми потоками. Из соображений производительности рекомендуется сделать потоки как можно более независимыми. Я считаю, что мне это действительно нужно, а может быть, и нет.

Пункт 3. Вы можете используйте простой замок. Я черпал вдохновение из https://codereview.stackexchange.com/questions/160277/implementing-a-thread-safe-lrucache и придумал следующее, которое я считаю безопасным для использования в моих целях

import threading

stubs = {}
lock = threading.Lock()


def maybe_new_stub(host):
    """ returns stub from cache and populates the stubs cache if new is created """
    with lock:
        if host not in stubs:
            channel = grpc.insecure_channel('%s:6666' % host)
            stub = cli_pb2_grpc.BrkStub(channel)
            stubs[host] = stub
        return stubs[host]

Пункт 4. лучше всего использовать существующую библиотеку. Я пока не нашел ни одного, за кого готов поручиться.

    Ничего не найдено.

Добавить ответ:
Отменить.