Потокобезопасный кеш объекта Python

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

http://freshmeat.net/projects/lrucache/

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

Спасибо!

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/

Пункт 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. Было бы лучше использовать существующую библиотеку. Я не нашел ничего, за что готов поручиться.

Для потокаобезопасного объекта вы хотите threading.local:

from threading import local

safe = local()

safe.cache = {}

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

Я не уверен, что какой-либо из этих ответов делает то, что вы хотите.

У меня аналогичная проблема, и я использую заменяющую замену для lrucache, называемую cachetools, которая позволяет вам передать блокировку, чтобы сделать его немного безопаснее.

Другие вопросы по тегам