我试图理解Python中的Threads。
代码
现在我遇到了一个问题,我在一个简单的课程中包含了这个问题:
# -*- coding: utf-8 -*-
import threading
class myClassWithThread(threading.Thread):
__propertyThatShouldNotBeShared = []
__id = None
def __init__(self, id):
threading.Thread.__init__(self)
self.__id = id
def run(self):
while 1:
self.dummy1()
self.dummy2()
def dummy1(self):
if self.__id == 2:
self.__propertyThatShouldNotBeShared.append("Test value")
def dummy2(self):
for data in self.__propertyThatShouldNotBeShared:
print self.__id
print data
self.__propertyThatShouldNotBeShared.remove(data)
obj1 = myClassWithThread(1)
obj2 = myClassWithThread(2)
obj3 = myClassWithThread(3)
obj1.start()
obj2.start()
obj3.start()
说明
这是班级的作用: 该类有两个属性:
__id
,它是对象的标识符,在调用构造函数时给出__propertyThatShouldNotBeShared
是一个列表,将包含文本值现在方法
run()
包含一个无限循环,我在其中调用dummy1()
然后dummy2()
dummy1()
只有当对象的__propertyThatShouldNotBeShared
等于2 __id
添加到值“测试值”
dummy2()
检查列表__propertyThatShouldNotBeShared
的大小是否严格优于0
__propertyThatShouldNotBeShared
中的每个值,它会输出__propertyThatShouldNotBeShared
这是我启动程序时得到的输出:
21
Test valueTest value
2
Test value
Exception in thread Thread-2:
Traceback (most recent call last):
File "E:\PROG\myFace\python\lib\threading.py", line 808, in __bootstrap_inner
self.run()
File "E:\PROG\myFace\myProject\ghos2\src\Tests\threadDeMerde.py", line 15, in run
self.dummy2()
File "E:\PROG\myFace\myProject\ghos2\src\Tests\threadDeMerde.py", line 27, in dummy2
self.__propertyThatShouldNotBeShared.remove(data)
ValueError: list.remove(x): x not in list
问题
正如您在输出的第一行中看到的那样,我得到了这个“1”......这意味着,在某些时候,ID为“1”的对象会尝试在屏幕上打印某些内容......实际上确实如此! 但这应该是不可能的! 只有ID为“2”的对象才能打印任何内容!
此代码有什么问题?或者我的逻辑有什么问题?
答案 0 :(得分:7)
问题在于:
class myClassWithThread(threading.Thread):
__propertyThatShouldNotBeShared = []
它为共享的所有对象定义一个列表。你应该这样做:
class myClassWithThread(threading.Thread):
def __init__(self, id):
self.__propertyThatShouldNotBeShared = []
# the other code goes here
答案 1 :(得分:1)
这里有两个问题 - 您询问的问题,线程安全问题,以及您没有问题的问题,类和实例属性之间的区别。
后者导致了你的实际问题。类属性由类的所有实例共享。它与在单个线程上还是在多个线程上访问这些实例无关;每个人都只有一个__propertyThatShouldNotBeShared
。如果需要实例属性,则必须在实例上定义它,而不是在类上定义。像这样:
class myClassWithThread(threading.Thread):
def __init__(self, id):
self.__propertyThatShouldNotBeShared = []
一旦你这样做,每个实例都有自己的__propertyThatShouldNotBeShared副本,并且每个实例都存在于自己的线程中,因此不存在线程安全问题。
但是,您的原始代码确实存在线程安全问题。
几乎没有任何东西是自动线程安全的(又名“同步”);异常(如queue.Queue
)将明确说明,并且专门用于线程编程。
您可以通过三种方式避免这种情况:
最后一个当然是最灵活的,也是最复杂的。事实上,它是人们考虑线程编程的原因的中心。
简短版本是,在您修改或访问共享可变数据(如self.__propertyThatShouldNotBeShared
)的任何地方,您需要持有某种同步对象,例如Lock
。例如:
class myClassWithThread(threading.Thread):
__lock = threading.Lock()
# etc.
def dummy1(self):
if self.__id == 2:
with self.__lock:
self.__propertyThatShouldNotBeShared.append("Test value")
如果您坚持使用CPython和内置类型,您通常可以忽略锁定。但是,在线程编程中“经常”只是“始终在测试和调试期间,直到发布或大型演示,当它突然开始失败时”的同义词。除非你想学习全局解释器锁和内置类型在CPython中如何工作的规则,否则不要依赖于此。
答案 2 :(得分:0)
Python中的类变量就是:所有类的实例共享。您需要一个实例变量,通常在__init__
内定义。删除类级声明(以及双引导下划线,它们用于名称修改,这里不需要。)