python 个线程没有死

python threads not dying

我有以下代码生成 10 个从文件列表复制文件的线程。我为不同的文件列表一遍又一遍地调用它,我发现一旦 fileQueue 运行 出来,线程似乎并没有死......我注意到代码似乎随着时间的推移而变慢操作,然后我在线程内崩溃并开始看到 "Exception in thread Thread-45:"!

这是我的代码,根据我在阅读手册时所知道的一切,这是非常干净和简单的:

import Queue, threading
from PyQt4 import QtCore, QtGui
import shutil

fileQueue = Queue.Queue()

class ThreadedCopy:
    totalFiles = 0
    copyCount = 0
    lock = threading.Lock()

    def __init__(self, inputList, progressBar=False):
        self.totalFiles = len(inputList)

        print str(self.totalFiles) + " files to copy."

        if progressBar:
            progressBar = QtGui.QProgressDialog("Copying files...", "Cancel", 0, self.totalFiles)
            progressBar.setMinimumDuration(0)
            progressBar.setWindowModality(QtCore.Qt.WindowModal)
            self.threadWorkerCopy(inputList, progressBar)
        else:
            self.threadWorkerCopy(inputList)


    def CopyWorker(self, progressBar):
        while True:
            fileName = fileQueue.get()
            shutil.copy(fileName[0], fileName[1])
            fileQueue.task_done()
            with self.lock:
                self.copyCount += 1
                if not progressBar:
                    print str(self.copyCount) + "of" + str(self.totalFiles)
                    percent = (self.copyCount * 100) / self.totalFiles
                    print "File copy: " + str(percent) + "%"
                else:
                    progressBar.setValue(self.copyCount)


    def threadWorkerCopy(self, fileNameList, progressBar=False):
        threadCount = 10
        for i in range(threadCount):
            t = threading.Thread(target=self.CopyWorker, args=(progressBar,))
            t.daemon = True
            t.start()
        for fileName in fileNameList:
            fileQueue.put(fileName)

        fileQueue.join()

有谁知道为什么线程在这段代码的调用之间没有干净利落地死掉?据我了解,一旦 fileQueue 运行 出来,它们就应该安静地死去!

编辑:这是固定代码

import Queue, threading
from PyQt4 import QtCore, QtGui
import shutil


fileQueue = Queue.Queue()

class ThreadedCopy:
    totalFiles = 0
    copyCount = 0
    lock = threading.Lock()

    def __init__(self, inputList, progressBar=False):
        self.totalFiles = len(inputList)

        print str(self.totalFiles) + " files to copy."

        if progressBar:
            progressBar = QtGui.QProgressDialog("Copying files...", "Cancel", 0, self.totalFiles)
            progressBar.setMinimumDuration(0)
            progressBar.setWindowModality(QtCore.Qt.WindowModal)
            self.threadWorkerCopy(inputList, progressBar)
        else:
            self.threadWorkerCopy(inputList)


    def CopyWorker(self, progressBar):
        while True:
            fileName = fileQueue.get()
            if fileName is None:
                fileQueue.task_done()
                break

            shutil.copy(fileName[0], fileName[1])
            fileQueue.task_done()

            with self.lock:
                self.copyCount += 1
                if not progressBar:
                    percent = (self.copyCount * 100) / self.totalFiles
                    print "File copy: " + str(percent) + "%"
                else:
                    progressBar.setValue(self.copyCount)


    def threadWorkerCopy(self, fileNameList, progressBar=False):
        threads = []
        threadCount = 10

        for fileName in fileNameList:
            fileQueue.put(fileName)
        for i in range(threadCount):
            t = threading.Thread(target=self.CopyWorker, args=(progressBar,))
            t.daemon = True
            t.start()
            threads.append(t)
            fileQueue.put(None)
        for t in threads:
            t.join()

您可能忘记为每个线程调用 .join。来自 documentation

需要在fileQueue.join()之后添加代码。但是你应该在 t.start() 之后将所有线程添加到 list(看一个例子)

for i in range(threadCount):
    fileQueue.put(None)
for t in threads:
    t.join()

为什么你认为线程会死掉? CopyWorker 中没有任何内容可以跳出 while True 循环,因此我希望线程能够无限期地保持活动状态。一旦所有项目都被消耗掉,它们将被永久阻止尝试 get 从空队列中获取另一个值,但它们不会退出或释放它们的资源。

如果您希望您的线程在没有更多工作要做时退出,您需要告诉它们这样做。这样做的一种常见方法是通过队列发送标记值,消费线程会将其识别为没有更多数据的信号。您需要为您启动的每个线程发送一份哨兵副本。这是基于您当前代码的快速未经测试的解决方案。我使用 None 作为标记,因为它看起来不像是文件名的正常值。

def CopyWorker(self, progressBar):
    while True:
        fileName = fileQueue.get()
        if fileName is None:             # check for sentinel value here
            fileQueue.task_done()
            return
        shutil.copy(fileName[0], fileName[1])
        fileQueue.task_done()
        with self.lock:
            self.copyCount += 1
            if not progressBar:
                print str(self.copyCount) + "of" + str(self.totalFiles)
                percent = (self.copyCount * 100) / self.totalFiles
                print "File copy: " + str(percent) + "%"
            else:
                progressBar.setValue(self.copyCount)


def threadWorkerCopy(self, fileNameList, progressBar=False):
    threadCount = 10
    for i in range(threadCount):
        t = threading.Thread(target=self.CopyWorker, args=(progressBar,))
        t.daemon = True
        t.start()
    for fileName in fileNameList:
        fileQueue.put(fileName)
    for i in range(threadCount):     # send sentinel values from here
        fileQueue.put(None)
    fileQueue.join()

您还可以执行一些其他操作,为简单起见,我已将其省略。例如,保留对您启动的每个线程的引用并从主线程 join 保留对它们的引用可能是个好主意,以确保它们都已退出。也许这可以替代 joining 队列。如果线程正常退出,线程也没有理由成为守护进程。

您还可以重新排序一些代码,这样就不需要两个 for i in range(threadCount) 循环。如果您 put 首先将所有值放入队列,然后再启动线程,您可以将两个循环组合起来。