多个线程打印混合在一起

时间:2015-09-29 10:06:23

标签: python multithreading thread-safety

我是第一次尝试多线程。在我创建了一组继承自Queue.Queue的对象后,我使用threading.Thread并将数据放入其中。该脚本下载了一系列文件并且工作得非常好,我下载了它,它已经证明比我的旧文件快得多。

但是,我的线程以print命令开头,表明它已经开始下载。只是一个简单的"下载C:\ foo.bar"。首次创建队列时,所有这些打印命令都会粘在一起,然后所有新行都会出现。

以下是所涉及代码的基本概念:

import Queue
import threading

queue = Queue.Queue()

class ThreadDownload(threading.Thread):
    """Threaded Download"""

    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            data = self.queue.get()

            print ("Downloading {}".format(data))
            #download_file(data)
            time.sleep(10)

            self.queue.task_done()

for i in range(4):
    t = ThreadDownload(queue)
    t.setDaemon(True)
    t.start()

#for d in data:
for d in range(20):
    queue.put(d)

queue.join()

请注意,download_file是来自第三方图书馆的功能,人们不太可能知道或无法轻松访问,因此我将其排除在外,以支持人们为其他人拨打其他时间测试。与data类似,数据形式与问题无关,因此我建议人们使用range以便轻松测试。

这里有什么输出:

Downloading C:\foo.barDownloading C:\foo.barDownloading C:\foo.barDownloading C:\foo.bar



Downloading C:\foo.bar
Downloading C:\foo.bar
Downloading C:\foo.bar

原因似乎是因为这些线程同时开始运行。如果我添加time.sleep(0.01)我可以阻止它,但这是一个hacky方法。而且我还担心这可能意味着,如果两次下载恰好在同一个瞬间开始,则会再次发生。

有没有办法在这里实际执行分离,所以我不能解决这个问题?我听说你不应该让线程处理UI,这通常是在重绘进度条的情况下。此外,我不确定是否有一种方便的方法可以记录线程中队列中的某个项目是否已经被线程占用,但也许我已经错过了它。

2 个答案:

答案 0 :(得分:3)

拥有一个带有单个线程的队列(称之为控制台线程),负责将消息写出。要编写一些你生成输出的东西,然后把它放在队列中,控制台线程会在它到达时正确地写它。

这样就有一个线程负责将内容写入控制台,你可以准确控制输出内容。

答案 1 :(得分:0)

Sorinanswer的实现:

# printt.py
from __future__ import annotations
from queue import Queue
import threading
from typing import Optional, TextIO

class _Param:
    def __init__(self,
                 *args,
                 sep: str=' ',
                 end: str='\n',
                 file: Optional[TextIO]=None,
                 flush: bool=False):
        self._args = args
        self._sep: str = sep
        self._end: str = end
        self._file: TextIO = file
        self._flush: bool = flush

    @property
    def args(self):
        return self._args

    @property
    def sep(self) -> str:
        return self._sep

    @property
    def end(self) -> str:
        return self._end

    @property
    def file(self) -> Optional[TextIO]:
        return self._file

    @property
    def flush(self) -> bool:
        return self._flush

_print_queue: Queue[_Param] = Queue()


def _printer():
    while True:
        p = _print_queue.get()
        print(*p.args, sep=p.sep, end=p.end, file=p.file, flush=p.flush)


_print_task = threading.Thread(target=_printer)
_print_task.start()


def printt(*args,
           sep: str=' ',
           end: str='\n',
           file: Optional[TextIO]=None,
           flush: bool=False):
    """
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

    Thread safe print. Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    file:  a file-like object (stream); defaults to the current sys.stdout.
    flush: whether to forcibly flush the stream.
    """
    _print_queue.put(_Param(*args, sep=sep, end=end, file=file, flush=flush))

显然,对于全线程安全打印,您必须在将要使用print()的任何地方都使用printt(),因为它只是序列化了print()在后台的使用。