Python class 范围和线程

Python class scope and threading

我对 python class 范围有点困惑。还有关于它与线程的关系。下面是一个最小的工作示例。我创建了一个 B 的实例并将其传递给 A.

的实例
  1. 按我的理解A应该是制作自己的本地副本B_instance。显然这并没有发生,因为每次我通过任何可以想象的方式修改 B_instance 的属性时,我都会看到 A_instanceB_instance 的变化(打印 1 -6)。这是否意味着 A_instance.other_class 被视为全局?有没有办法让 A_instance.other_class 本地化?这样修改它就不会改变 B_instance itself.

  2. 问题的第二部分与线程有关。我知道访问 class 属性不是线程安全的,所以我使用了锁。但是,如果您查看打印语句,我希望 "lock released" 在 "modified by main thread" 之前打印。我错过了什么?两个锁似乎都在锁定其他东西,因为显然可以获取主线程中的锁,即使 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. 没有。当您创建 B 的实例并将其传递给 A 构造函数时,将传递对同一 B 实例的 reference 。没有复制。如果你想要那个,你必须自己复制一份。一种方法是使用 copy.deepcopy(但请注意,某些类型无法复制——threading.Lock 实例就是其中之一)。本质上,新引用(您存储在 A 的 self.other_class 中)是同一实例的另一个名称。

  2. 您的两个代码块能够同时执行的原因是您创建了两个不同的锁。您在 class A 构造函数中创建了一个——那个被 thread_modify_attr locked/unlocked。另一个是在子线程创建之前在主线程代码中创建的,并由主线程锁定和解锁。如果您希望两者使用相同的锁,请将锁作为参数传递(引用)到线程函数中。因为它们不是同一个锁,所以没有什么可以阻止两个线程并发执行。

  3. 锁可以保护任何你想保护的东西。您的代码有责任确定锁保护的内容。也就是说,您 可以 在 class 的构造函数中创建一个锁。然后,无论何时任何引用 class 实例,您都可以使用与该实例关联的锁来同步访问。一种常见的模式是在 class 内集中控制对象资源。因此,外部代理只需要求对象做某事(通过调用其方法之一)。然后该对象在内部使用锁来保护其数据结构。

Python 描述了范围规则 here

只是@Gil 上面的回答的扩展

  1. 您可以在我为您创建的 this PythonTutor snippet 上看到 Python 如何处理您的 类、它们的实例以及对它们的引用的直观解释。

Lock 可以作为上下文管理器运行,因此您可以在 with 语句中使用它,并且当 with 块因任何原因(例如异常)退出时它会自动释放。

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

注意: 如果上面的 link 已经失效,只需直接转到 PythonTutor.com 并复制并粘贴此修改后的代码版本。唯一的变化是我删除了与线程有关的所有内容,因为站点不支持它。

class A:

    def __init__(self, other_class):
        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


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)

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