Область действия и многопоточность класса Python - PullRequest
0 голосов
/ 02 декабря 2018

Я немного запутался в области видимости классов Python.Также о его связи с потоками.Ниже приведен минимальный рабочий пример.Я создал экземпляр B и передал его экземпляру A.

  1. Как я понимаю, A должен создать свою собственную локальную копию B_instance.По-видимому, этого не происходит, поскольку каждый раз, когда я изменяю атрибут B_instance любым мыслимым способом, я вижу изменение как в A_instance, так и в B_instance (печатает 1-6).Означает ли это, что A_instance.other_class считается глобальным?Есть ли способ сделать A_instance.other_class локальным?так что его изменение не изменит сам B_instance.

  2. Вторая часть вопроса связана с многопоточностью.Я знаю, что доступ к атрибутам класса не является потокобезопасным, поэтому я использую блокировки.Однако, если вы посмотрите на операторы print, я бы ожидал, что «блокировка снята» будет напечатана перед «изменением в основном потоке».Что мне не хватает?Кажется, что обе блокировки блокируют что-то еще, поскольку блокировка в основном потоке может быть получена, даже если в A_instance все еще действует.Мне кажется, что это противоречит моим выводам из первой части.

  3. Можно ли получить блокировку для всего объекта, а не только его атрибута или метода?


class A:

    def __init__(self, other_class):
        self.lock = threading.Lock()
        self.other_class = other_class

    def increment_B(self):
        self.other_class.B_attr_1 += 1

    def set_B(self):
        self.other_class.B_attr_1 = 10

    def thread_modify_attr(self):
        self.lock.acquire()
        self.other_class.B_attr_1 = "modified by class"
        time.sleep(10)
        self.lock.release()
        print("lock released")


class B:

    def __init__(self):
        self.B_attr_1 = 0


if __name__ == "__main__":

    B_instance = B()

    A_instance = A(B_instance)

    print("1:", B_instance.B_attr_1)

    A_instance.other_class.B_attr_1 += 1
    print("2:", B_instance.B_attr_1)

    A_instance.other_class.B_attr_1 = 10
    print("3:", B_instance.B_attr_1)

    A_instance.increment_B()
    print("4:", B_instance.B_attr_1)

    A_instance.set_B()
    print("5:", B_instance.B_attr_1)

    B_instance.B_attr_1 = 0
    print("6:", A_instance.other_class.B_attr_1)

    lock = threading.Lock()

    t = threading.Thread(target=A_instance.thread_modify_attr)
    t.start()
    print("thread started")

    print(B_instance.B_attr_1)

    lock.acquire()
    B_instance.B_attr_1 = "modified by main thread"
    lock.release()

    print(B_instance.B_attr_1)
    print("done")

    t.join()

Результаты:

1: 0
2: 1
3: 10
4: 11
5: 10
6: 0                                                                                                             
thread started                                                                                                      
modified by class                                                                                                          
modified by main thread
done
lock released

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

1 Ответ

0 голосов
/ 03 декабря 2018
  1. Нет.Когда вы создаете экземпляр B и передаете его конструктору A, ссылка на тот же экземпляр B передается.Копия не сделана.Если вы хотите этого, вы должны будете сделать копию самостоятельно.Одним из способов является использование copy.deepcopy (хотя обратите внимание, что существуют определенные типы, которые не могут быть скопированы - экземпляр threading.Lock является одним из них).По сути, новая ссылка (которую вы храните в A self.other_class) - это другое имя для того же экземпляра.

  2. Причина, по которой ваши два блока кода могут выполняться одновременно, заключается в том, чточто вы создали две разные блокировки .Вы создаете один в конструкторе класса A - тот заблокирован / разблокирован thread_modify_attr.Другой создается в коде основного потока непосредственно перед созданием подпотока и блокируется и разблокируется основным потоком.Если вы хотите, чтобы они использовали одну и ту же блокировку, передайте (ссылку на) блокировку в функцию потока в качестве аргумента.Поскольку они не являются одной и той же блокировкой, ничто не мешает одновременному выполнению обоих потоков.

  3. Блокировки могут защищать все, что вы хотите, чтобы они защищали.Ваш код отвечает за определение того, что защищено блокировкой.При этом вы можете создать блокировку в конструкторе вашего класса.Затем, когда что-либо ссылается на экземпляр класса, вы можете использовать блокировку, связанную с экземпляром, для синхронизации доступа.Распространенным шаблоном является централизованное управление ресурсами объекта в классе.Так что внешний агент просто просит объект сделать что-то (вызывая один из его методов).Затем объект использует внутреннюю блокировку для защиты своих структур данных.

описаны правила области видимости Python здесь

...