如何在python中的线程之间安全地传递变量值

时间:2014-07-30 00:22:42

标签: python multithreading queue

我在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()

据我所知,队列对象只能用于放置和获取值,这显然不适合我。这就是我在这里"安全"?如果没有,我该如何编程,以便我可以安全地从多个不同的线程访问变量?

1 个答案:

答案 0 :(得分:7)

每当你有线程修改其他线程可以看到的值时,你就会遇到安全问题。担心的是,当另一个线程正在修改它时,线程将尝试修改一个值,该线程具有风险和未定义的行为。所以不,你的切换切换代码不安全。

重要的是要知道改变变量的值不能保证原子。如果某个操作是原子操作,则意味着操作将始终在一个不间断的步骤中发生。 (这与数据库定义略有不同。)更改变量值(尤其是列表值)通常可以在处理器级别上执行多个步骤。当您使用线程时,所有这些步骤都不能保证在另一个线程开始工作之前一次完成。当线程B突然接管时,线程A完全有可能在改变变量x的中途。然后,如果线程B尝试读取变量x,它将不会找到正确的值。更糟糕的是,如果线程B尝试修改变量x,而线程A在做同样的事情的过程中,可能会发生不好的事情。 每当你有一个值可以某种方式改变的变量时,所有对它的访问都需要成为线程安全的。

如果您要修改变量而不是传递消息,那么您应该使用Lock对象。

在您的情况下,您在顶部有一个全局Lock对象:

from threading import Lock

switch_lock = Lock()

然后,您将使用acquirerelease函数包围关键代码段。

    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语句很好地集成在一起。如果发生异常,这具有自动解锁的额外好处。因此,您可以执行此操作,而不是上述acquirerelease系统:

    for i in switches:
        with switch_lock:
            i.toggle()