Python中的线程:类属性(列表)不是线程安全的?

时间:2013-12-30 20:32:12

标签: python multithreading list class object

我试图理解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中的每个值,它会输出
    • 的ID
    • __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”的对象才能打印任何内容!

此代码有什么问题?或者我的逻辑有什么问题?

3 个答案:

答案 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__内定义。删除类级声明(以及双引导下划线,它们用于名称修改,这里不需要。)