2017-09-20 46 views
1

我有下面的代码,它产生10个线程,从文件列表中复制文件。我一次又一次地调用这个文件的不同列表,我发现线程似乎并没有死在一旦fileQueue用完了......我注意到,代码似乎放慢了长操作,然后我在线程内发生崩溃,并开始看到“Thread Thread-45中的异常:”!python线程不会死

这里是我的代码,从我知道一切都在阅读说明书,这是非常干净和简单:

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

回答

1

为什么你认为线程会死吗? CopyWorker没有任何内容突破while True循环,所以我期望这些线程无限期地保持活动状态。一旦所有物品都被消耗完了,它们将被永久封锁,试图从空队列中获得另一个值,但它们不会退出或释放其资源。

如果您希望线程在没有更多工作要完成时退出,您需要告诉他们这样做。这样做的一种常见方式是在队列中发送一个标记值,消耗线程将识别为没有更多数据的信号。您需要为每个已开始的线程发送一份哨兵副本。这是一个基于您当前代码的快速未经测试的解决方案。我使用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() 

还有其他一些你可以做的事情,为简单起见我省略了。例如,最好从主线程中保存对每个线程的引用,并确保它们全部退出。或许,这可能是排队的替代方案。如果线程正常退出,线程也不会成为守护进程。

您也可以重新排列一些代码,以便不需要两个for i in range(threadCount)循环。如果您将put的所有值放入队列中,则在启动线程之前,可以组合这两个循环。

+0

谢谢,这非常有帮助!我对while循环没有关闭有同样的想法,但是我认为这不是问题,因为我从文档[doc](https://docs.python.org/2/library/queue.html )。我用我的固定代码更新了我的问题。 – Spencer

+0

链接的示例代码可以很好地作为完成排队任务完成后立即退出的完整脚本。但是对于长时间运行的程序中的函数来说这是一个非常糟糕的方法。这种设计会“泄漏”线程,但是在一段短暂的脚本中是可以的,因为当解释器退出时(线程被设置为守护进程以防止解释器关闭),它们将被清理干净。在您的固定代码中,您可以放弃't.daemon = True'行和'task_done'调用,因为您不再'加入'队列。 – Blckknght

+0

我明白了,没有理由让它们成为守护神,在这一点上,task_done只是多余的。我以为我出于某种原因仍然需要它,但是我发现它仅适用于queue.join函数。谢谢! – Spencer

0

你可能忘了打电话给.join为每个线程。来自documentation

需要在fileQueue.join()后面添加代码。但是,你应该t.start()毕竟线程添加到list(看个例)

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

感谢AndMar,但这并没有做到这一点,我加入了基本上做同样事情的队列(在继续前等待队列完成)。也就是说,我同意你和Blckknght的意见,认为这比加入队列更好,因此我已将它合并到我的代码中! – Spencer