我在python文档中读到Queue.Queue()是一种在不同线程之间传递变量的安全方法。我真的不知道多线程存在安全问题。对于我的应用程序,我需要使用可以从多个不同线程访问的变量开发多个对象。现在我只是让线程直接访问对象变量。我不会在这里显示我的代码,因为它的方式太多了,但这是一个展示我正在做的事情的例子。
from threading import Thread
import time
import random
class switch:
def __init__(self,id):
self.id=id
self.is_on = False
def self.toggle():
self.is_on = not self.is_on
switches = []
for i in range(5):
switches[i] = switch(i)
def record_switch():
switch_record = {}
while True:
time.sleep(10)
current = {}
current['time'] = time.srftime(time.time())
for i in switches:
current[i.id] = i.is_on
switch_record.update(current)
def toggle_switch():
while True:
time.sleep(random.random()*100)
for i in switches:
i.toggle()
toggle = Thread(target=toggle_switch(), args = ())
record = Thread(target=record_switch(), args = ())
toggle.start()
record.start()
据我所知,队列对象只能用于放置和获取值,这显然不适合我。这就是我在这里"安全"?如果没有,我该如何编程,以便我可以安全地从多个不同的线程访问变量?
答案 0 :(得分:7)
每当你有线程修改其他线程可以看到的值时,你就会遇到安全问题。担心的是,当另一个线程正在修改它时,线程将尝试修改一个值,该线程具有风险和未定义的行为。所以不,你的切换切换代码不安全。
重要的是要知道改变变量的值不能保证原子。如果某个操作是原子操作,则意味着操作将始终在一个不间断的步骤中发生。 (这与数据库定义略有不同。)更改变量值(尤其是列表值)通常可以在处理器级别上执行多个步骤。当您使用线程时,所有这些步骤都不能保证在另一个线程开始工作之前一次完成。当线程B突然接管时,线程A完全有可能在改变变量x
的中途。然后,如果线程B尝试读取变量x
,它将不会找到正确的值。更糟糕的是,如果线程B尝试修改变量x
,而线程A在做同样的事情的过程中,可能会发生不好的事情。 每当你有一个值可以某种方式改变的变量时,所有对它的访问都需要成为线程安全的。
如果您要修改变量而不是传递消息,那么您应该使用Lock
对象。
在您的情况下,您在顶部有一个全局Lock
对象:
from threading import Lock
switch_lock = Lock()
然后,您将使用acquire
和release
函数包围关键代码段。
for i in switches:
switch_lock.acquire()
current[i.id] = i.is_on
switch_lock.release()
for i in switches:
switch_lock.acquire()
i.toggle()
switch_lock.release()
一次只能有一个线程获得一个锁(无论如何都是这种锁)。当任何其他线程尝试时,它们将被阻止并等待锁再次释放。因此,通过在代码的关键部分放置锁定,您可以使多个线程无法随时查看或修改给定的开关。你可以把它放在你希望一次保留为一个线程的任何代码中。
编辑:正如martineau所指出的,如果你使用的是拥有它的Python版本,那么锁与with
语句很好地集成在一起。如果发生异常,这具有自动解锁的额外好处。因此,您可以执行此操作,而不是上述acquire
和release
系统:
for i in switches:
with switch_lock:
i.toggle()