потокобезопасный кеш Python - PullRequest
9 голосов
/ 17 октября 2008

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

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

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

Спасибо!

Ответы [ 5 ]

9 голосов
/ 18 октября 2008

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

8 голосов
/ 18 октября 2008

Ну, многие операции в 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

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

4 голосов
/ 18 октября 2008

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

http://www.danga.com/memcached/

1 голос
/ 02 июля 2013

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

from threading import local

safe = local()

safe.cache = {}

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

0 голосов
/ 26 июня 2018

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...