python类作用域和线程

时间:2018-12-02 13:22:44

标签: python-3.x python-2.7 scope thread-safety python-multithreading

我对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. 问题的第二部分与线程相关。我知道访问类属性不是线程安全的,因此我正在使用锁。但是,如果您看一下打印语句,我希望在“由主线程修改”之前先打印“锁定释放”。我想念什么?这两个锁似乎都在锁定其他东西,因为即使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作用域详细信息的好地方,对此我将不胜感激。

2 个答案:

答案 0 :(得分:0)

  1. 否。创建B的实例并将其传递给A构造函数时,将传递对同一B实例的 reference 。没有完成复制。如果需要,您必须自己制作一个副本。一种方法是使用copy.deepcopy(尽管请注意,某些类型无法复制-threading.Lock实例就是其中之一)。本质上,新引用(存储在A的self.other_class中)是同一实例的另一个名称。

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

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

python作用域规则(在here中进行了描述

答案 1 :(得分:0)

只是@Gil上面答案的扩展

  1. 您可以在我为您创建的this PythonTutor snippet上看到有关Python如何处理您的类,其实例以及对它们的引用的直观说明。

锁可以用作上下文管理器,因此您可以在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")

注意::如果上面的链接失效了,只需直接转到the site,然后复制并粘贴此修改后的代码版本即可。唯一的变化是我删除了所有与线程相关的内容,因为该网站不支持。

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")