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