【问题标题】:Python thread locks vs forced timer delaysPython线程锁与强制定时器延迟
【发布时间】:2015-06-11 06:08:33
【问题描述】:

据我了解,当您在 Python 中实现全局锁时,这应该为激活锁的线程保留标准输出,从而防止其他线程在线程释放锁之前使用标准输出。

这是否意味着在下面的代码中,线程“a”中的循环应该在线程“b”中的函数输出任何内容之前完成?当我运行它时,线程“c”打印的“7”有时会在“a”的输出中交错。

我希望输出始终如下:

 5
 5
 5
 5
 5
 6
 7

但我得到了:

 5
 7
 5
 5
 5
 6

代码:

import threading, time

thelock = threading.Lock()

def afunc(var):
    with thelock:
        for i in range(5):
            time.sleep(.0002)
            print(var)

def bfunc(var):
    print(var)

a=threading.Thread(target=afunc, args=(5,))
b=threading.Thread(target=bfunc, args=(6,))
c=threading.Thread(target=bfunc, args=(7,))
a.start()
b.start()
c.start()

这适用于 OS X 10.10.3 中的 Python 3.4.3。直接在 OS X 终端或 PyCharm 4.5.1 中运行文件时会发生相同的行为。

【问题讨论】:

    标签: python multithreading thread-safety


    【解决方案1】:

    你没有实现全局锁,因为没有这样的东西。我的意思是,有但那是 Global Interpreter Lock,它是一种语言功能,而您没有实现它。

    吉尔: http://en.wikipedia.org/wiki/Global_Interpreter_Lock

    现在,关于您正在实施的锁:

    threading.Lock() - 一个工厂函数,返回一个新的 primitive 锁定对象。一旦一个线程获取它,随后的尝试获取 它阻塞,直到它被释放;任何线程都可以释放它。

    线程查看官方文档:https://docs.python.org/2/library/threading.html

    一般来说,当您将某个资源锁定到临界区时,该资源只会被锁定以访问共享资源也必须获得锁定的线程。在您的示例中,函数 b 在写入标准输出之前不会尝试获取锁。

    此外,我相当肯定这不会像您期望的那样工作,并且无论如何输出都会混淆,因为标准输出没有被锁定。要获得干净的输出,您可以让线程将消息传递到某个线程安全的共享队列,然后只有一个线程从该队列写入标准输出。

    【讨论】:

    • 啊,谢谢!是的,我没有看到如何使用锁的全部范围。我认为在函数之外声明意味着它以某种方式为全局资源提供了一些通用锁。相反,单个锁对象似乎提供了跨函数或线程的接力棒传递。
    【解决方案2】:

    threading.Lock 根本不影响标准输出的使用。它仅用于创建所谓的临界区,其中一次只有一个线程可以执行受同一锁实例保护的所有临界区中的代码。

    如果您需要序列化输出,您必须自己实现,可能基于锁或其他机制。

    例如,您可以有一个日志记录类,它在持有一些锁的同时缓冲其他线程的日志记录,但这并非微不足道,因为日志记录类不能仅仅“检查”某个锁是否正在使用,因为会导致竞争条件。通常阻止日志记录并不是一个好主意。但也许您正在考虑标准输出的其他用途。通常,如果您尝试在多线程程序中生成特定的标准输出,您可能应该明确地为所有输出使用锁或将输出的使用限制为仅一个线程。对于将输出限制为一个线程,queue.Queue 可能会有所帮助,因此多个线程可以生成完整的部分,然后由一个线程一次打印一个部分。

    【讨论】:

    • 谢谢!是的,我现在正在学习队列,但刚刚掀起这个场景来测试我对锁的理解。我的想法有点倒退。将锁视为某种虚拟指挥棒似乎是正确的,对于同一个指挥棒,线程或函数只有在持有指挥棒时才会运行。在被交给接力棒之前,它将被阻塞,而另一个线程拿着接力棒并做它的事情。在我上面的代码中,bfunc没有接力棒,只有在print语句前使用"with:thelock"才会被阻塞,所以当afunc完成打印后,bfunc就可以开始打印了。
    • 是的,这是一种看待它的方式。程序只有获得thelock的一根接力棒,才能通过with thelock:thelock.acquire()这一行。在 with 块或显式 thelock.release() 结束时,将返回指挥棒,以便该线程或另一个线程稍后可以再次使用它进入另一个受保护的块。每把锁只有一根警棍。如果你已经拿着指挥棒了,你仍然不能通过,因为你需要在输入受保护的代码时获取它。如果您需要线程能够“重用”接力棒,则需要使用 RLock。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-04
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 2011-10-18
    • 1970-01-01
    相关资源
    最近更新 更多