Использование глобального словаря с потоками в Python - PullRequest
30 голосов
/ 21 августа 2009

Является ли доступ / изменение значений словаря потокобезопасным?

У меня есть глобальный словарь foo и несколько потоков с идентификаторами id1, id2, ..., idn. Можно ли обращаться к значениям foo и изменять их без выделения блокировки, если известно, что каждый поток будет работать только со своим значением, связанным с идентификатором, например, поток с id1 будет работать только с foo[id1]?

Ответы [ 5 ]

46 голосов
/ 21 августа 2009

Предположим, CPython: да и нет. На самом деле безопасно извлекать / хранить значения из общего словаря в том смысле, что несколько одновременных запросов на чтение / запись не повредят словарь. Это связано с глобальной блокировкой интерпретатора («GIL»), поддерживаемой реализацией. То есть:

Тема A работает:

a = global_dict["foo"]

резьба B работает:

global_dict["bar"] = "hello"

резьба C работает:

global_dict["baz"] = "world"

не повредит словарь, даже если все три попытки доступа происходят в одно и то же время. Интерпретатор будет сериализовать их каким-то неопределенным образом.

Однако результаты следующей последовательности не определены:

Тема A:

if "foo" not in global_dict:
   global_dict["foo"] = 1

Резьба B:

global_dict["foo"] = 2

, поскольку тест / набор в потоке A не является атомарным (условие гонки "время проверки / время использования"). Итак, обычно лучше, если вы заблокируете вещи:

from threading import RLock

lock = RLock()

def thread_A():
    lock.acquire()
    try:
        if "foo" not in global_dict:
            global_dict["foo"] = 1
    finally:
        lock.release()

def thread_B():
    lock.acquire()
    try:
        global_dict["foo"] = 2
    finally:
        lock.release()
24 голосов
/ 21 августа 2009

Лучший, самый безопасный и переносимый способ заставить каждый поток работать с независимыми данными:

import threading
tloc = threading.local()

Теперь каждый поток работает с полностью независимым tloc объектом, даже если это глобальное имя. Поток может получать и устанавливать атрибуты на tloc, использовать tloc.__dict__, если ему нужен словарь и т. Д.

Локальное хранилище для потока исчезает в конце потока; чтобы потоки записывали свои окончательные результаты, пусть они put свои результаты, прежде чем они завершатся, в общий экземпляр Queue.Queue (который по своей сути поточно-ориентирован). Аналогично, начальные значения для данных, с которыми должен работать поток, могут быть аргументами, передаваемыми при запуске потока, или могут быть взяты из Queue.

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

15 голосов
/ 09 апреля 2015

Так как мне нужно что-то подобное, я приземлился здесь. Я суммирую ваши ответы в этом коротком фрагменте:

#!/usr/bin/env python3

import threading

class ThreadSafeDict(dict) :
    def __init__(self, * p_arg, ** n_arg) :
        dict.__init__(self, * p_arg, ** n_arg)
        self._lock = threading.Lock()

    def __enter__(self) :
        self._lock.acquire()
        return self

    def __exit__(self, type, value, traceback) :
        self._lock.release()

if __name__ == '__main__' :

    u = ThreadSafeDict()
    with u as m :
        m[1] = 'foo'
    print(u)

как таковой, вы можете использовать конструкцию with, чтобы удерживать блокировку во время игры в dict()

4 голосов
/ 21 августа 2009

GIL позаботится об этом, если вы используете CPython.

глобальная блокировка интерпретатора

Блокировка, используемая потоками Python для обеспечения выполнения только одного потока одновременно на виртуальной машине CPython. Это упрощает реализацию CPython, гарантируя, что никакие два процесса не могут одновременно обращаться к одной и той же памяти. Блокировка всего интерпретатора облегчает многопоточность интерпретатора за счет большей части параллелизма, обеспечиваемого многопроцессорными машинами. В прошлом были предприняты попытки создать интерпретатор с «свободной резьбой» (тот, который блокирует общие данные с более высокой степенью детализации), но до сих пор ни один из них не был успешным, поскольку в общем случае с одним процессором пострадала производительность.

См. являются ненужными блокировки в многопоточном коде python-код-из-за-gil .

1 голос
/ 31 августа 2015

Как это работает?:

>>> import dis
>>> demo = {}
>>> def set_dict():
...     demo['name'] = 'Jatin Kumar'
...
>>> dis.dis(set_dict)
  2           0 LOAD_CONST               1 ('Jatin Kumar')
              3 LOAD_GLOBAL              0 (demo)
              6 LOAD_CONST               2 ('name')
              9 STORE_SUBSCR
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

Каждая из приведенных выше инструкций выполняется с удержанием блокировки GIL, а инструкция STORE_SUBSCR добавляет / обновляет пару ключ + значение в словаре. Итак, вы видите, что обновление словаря является атомарным и, следовательно, потокобезопасным.

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