【问题标题】:Basic python multi-threading issue基本的python多线程问题
【发布时间】:2012-07-23 21:00:07
【问题描述】:

python 新手并试图理解多线程。这是Queue上的python文档中的一个示例

对于我的生活,我不明白这个例子是如何工作的。在 worker() 函数中,有一个无限循环。工人如何知道何时退出循环?似乎没有断裂条件。

最后,join 到底在做什么?我不应该加入线程吗?

def worker():
    while True:
        item = q.get()
        do_work(item)
        q.task_done()

q = Queue()
for i in range(num_worker_threads):
    t = Thread(target=worker)
    t.daemon = True
    t.start()

for item in source():
    q.put(item)

q.join()       # block until all tasks are done

还有一个问题,什么时候应该使用多线程,什么时候应该使用多处理?

【问题讨论】:

    标签: python multithreading thread-safety


    【解决方案1】:

    是的。你是对的。 worker 将永远运行。然而,由于队列只有有限数量的项目,最终worker 将在q.get() 处永久阻塞(因为队列中将不再有项目)。此时,worker 仍在运行是无关紧要的。 q.join() 阻塞,直到队列计数下降到 0(每当工作线程调用 q.task_done 时,计数下降 1)。之后,程序结束。无限阻塞线程与它的创建者一起死亡。

    【讨论】:

    • 啊我现在明白了。是否有任何网站/资源可以让我了解这些微妙之处?文档确实不是看起来最好的地方。
    • 除了主线程结束杀死子线程之外,所有这些信息都直接来自您自己链接的Queue 文档页面。
    • 感谢您的回答。我想我会接受它,因为它回答了我的主要问题。
    【解决方案2】:

    关于你的第二个问题,Python 中线程和进程的最大区别在于,主流实现使用全局解释器锁(GIL)来确保多个线程不会弄乱 Python 的内部数据结构。这意味着对于大部分时间在纯 Python 中进行计算的程序,即使使用多个 CPU,您也不会大大加快程序速度,因为一次只有一个线程可以保存 GIL。另一方面,多个线程可以在 Python 程序中轻松共享数据,在某些(但并非全部)情况下,您不必过多担心线程安全。

    多线程可以加速 Python 程序的地方是程序将大部分时间花在等待 I/O 上——磁盘访问,或者,尤其是现在,网络操作。 GIL 在执行 I/O 时不会被持有,因此许多 Python 线程可以在 I/O 绑定应用程序中同时运行。

    另一方面,对于多处理,每个进程都有自己的 GIL,因此您的性能可以扩展到您可用的 CPU 内核数量。不利的一面是,进程之间的所有通信都必须通过 multiprocessing.Queue 完成(它在表面上的作用非常类似于 Queue.Queue,但具有非常不同的底层机制,因为它必须跨进程边界进行通信)。

    由于通过线程安全或进程间队列工作可以避免很多潜在的线程问题,而且由于 Python 使它变得如此简单,multiprocessing 模块非常有吸引力。

    【讨论】:

    • 很好的答案,很遗憾我刚注册后无法投票。我正在做多线程/处理,因为我需要并行读取多个数据文件(数千个),从中读取一些信息,然后将文件写回磁盘上(再次写入数千个)。那么我应该使用多线程还是多处理?似乎我会做相当多的 I/O。
    • 我也不清楚如何“加入”这些流程。最后我有一个循环,循环遍历创建的每个进程,并调用 join,但我的代码仍然没有终止。
    • 如果主线程调用childThread.join(),子线程必须自行终止,上面的示例代码没有这样做。您的示例代码使用了 Queue.join(),这是非常不同的。可能是时候使用您当前拥有的代码并提出一个新问题了。
    • 多线程与多处理是一个艰难的判断要求,但幸运的是,由于 API 的相似性,它很容易切换。如果文件很大并且您没有对它们进行太多计算,那么多线程就可以了。如果您在读写少量数据的情况下进行大量计算,那么多处理可能是一个更大的胜利。
    • 感谢您的回答。它确实回答了我的问题,但由于我的主要问题是处理代码 sn-p 的解释,所以我会接受 Joel 的回答。
    【解决方案3】:

    同意 joel-cornett,大部分。我尝试在 python2.7 中运行以下 sn-p :

    from threading import Thread
    from Queue import Queue
    
    def worker():
        def do_work(item):
            print(item)
    
        while True:
            item = q.get()
            do_work(item)
            q.task_done()
    
    q = Queue()
    for i in range(4):
         t = Thread(target=worker)
         t.daemon = True
         t.start()
    
    for item in range(10):
        q.put(item)
    
    q.join()
    

    输出是:

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Exception in thread Thread-3 (most likely raised during interpreter shutdown):
    Traceback (most recent call last):
      File "/usr/lib/python2.7/threading.py", line 551, in __bootstrap_inner
      File "/usr/lib/python2.7/threading.py", line 504, in run
      File "abc.py", line 9, in worker
      File "/usr/lib/python2.7/Queue.py", line 168, in get
      File "/usr/lib/python2.7/threading.py", line 236, in wait
    <type 'exceptions.TypeError'>: 'NoneType' object is not callable
    

    我认为最可能的解释:

    由于任务耗尽后队列变空,父线程退出,从 q.join() 返回并销毁队列。子线程在收到“item = q.get()”中产生的第一个 TypeError 异常时终止,因为队列不再存在。

    【讨论】:

    • 如果我删除 q.join() 并确保父线程在处理完成之前终止,我不会得到与您相同的结果即使
    猜你喜欢
    • 1970-01-01
    • 2011-01-27
    • 2016-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-09
    • 1970-01-01
    相关资源
    最近更新 更多