我应该为仅存在于{类,方法}中的变量使用线程局部存储吗?
我正在用Python的Queue.Queue
类实现一个比较简单的线程池。我有一个生产者类,它包含了Queue
实例和一些方便的方法,还有一个消费者类,它是threading.Thread
的子类。我会根据一个整数为线程池中的每个线程(我想它们叫“工作线程”)实例化这个对象。
每个工作线程从队列中取出flag, data
,使用自己的数据库连接处理这些数据,然后把处理完的行的GUID放到一个列表里,这样生产者类就知道任务完成了。
虽然我知道其他模块也实现了我正在编写的功能,但我这样做的原因是为了更好地理解Python的线程是如何工作的。这引出了我的问题。
如果我把任何东西存储在函数的命名空间或者类的__dict__
对象中,这样做会是线程安全的吗?
class Consumer(threading.Thread):
def __init__(self, producer, db_filename):
self.producer = producer
self.conn = sqlite3.connect(db_filename) # Is this var thread safe?
def run(self):
flag, data = self.producer.queue.get()
while flag != 'stop':
# Do stuff with data; Is `data` thread safe?
我认为这两者都是线程安全的,以下是我的理由:
- 每次实例化一个类时,都会创建一个新的
__dict__
。在我上面描述的场景中,我认为没有其他对象会引用这个对象。(当然,如果我使用join()
功能,情况可能会复杂一些,但我并没有使用...) - 每次调用一个函数时,它会创建自己的命名空间,这个命名空间在函数的生命周期内存在。我没有把任何变量设为
global
,所以我不明白其他对象怎么会引用到函数的变量。
这篇文章在某种程度上回答了我的问题,但对我来说还是有点抽象。
谢谢你提前帮我澄清这个问题。
3 个回答
我觉得你大体上对你的假设是正确的,在你的情况下你很可能是对的。
不过,判断某个东西是否线程安全其实比你说的要复杂一些。
像 self.conn = sqlite3.connect(db_filename)
这样的调用可能不是线程安全的,因为sqlite3模块可能会共享一些状态,调用这个函数可能会有一些副作用。不过,我怀疑情况是这样的,和你一样,我认为它应该是生成一个全新的变量。
不仅仅是全局变量可能会有问题,从外部作用域获取可变变量也是个问题。
所以在
flag, data = self.producer.queue.get()
中的数据是否线程安全,取决于这些数据最初是在哪里产生的。不过,我认为这些数据应该是独立的(最好是不可变的)信息。如果是这样的话,那么所有的数据应该都是线程安全的。
为了让数据在多线程中安全使用,可以用copy.deepcopy()来创建数据的新副本,然后再把这个副本放到队列里。这样,生产者在下一轮循环中可以修改数据,而不会影响到消费者手里的副本,直到消费者处理完它。
你说得对,这个是线程安全的。局部变量(你称之为“函数命名空间”的那些)总是线程安全的,因为只有执行这个函数的线程才能访问它们。实例属性也是线程安全的,只要这个实例没有在多个线程之间共享。因为消费者类是从线程类继承来的,所以它的实例肯定不会在多个线程之间共享。
这里唯一的“风险”在于数据对象的值:理论上,生产者在把数据对象放进队列后可能还会继续持有这个对象,如果这个数据对象是可变的(你要确保理解“可变”是什么意思),那么在消费者使用它的时候,生产者可能会改变这个对象。如果生产者在把数据对象放进队列后就不再动它,那这个就是线程安全的。