Python线程偶尔会无法返回

时间:2014-07-03 16:13:58

标签: python multithreading

我正在研究一个多线程Python脚本,该脚本获取文件名列表并将它们放在队列中。大部分时间它都有效,但我偶尔会发现它卡住了,'ps -efL'会显示两个线程为python脚本打开。我用strace跟着它,并且返回了6个线程中的5个,但是一个只是在futex中等待永远等待。

以下是相关代码块。

threads = 6 

for fileName in fileNames:
  queue.put(fileName)

for i in range(threads):
  t = threading.Thread(target=get_backup_list, args=(queue,dbCreds,arguments.verbose,arguments.vault))
  activeThreads.append(t)
  t.start()

for activeThread in activeThreads:
  activeThread.join()


def get_backup_list(queue,dbCreds,verbosity,vault):
  backupFiles = []

  while True:
    if queue.empty() == True:
      return 
    fileName = queue.get()
    try:
      fileInfo = lookup_file_by_path(fileName,dbCreds,vault)
      if not fileInfo:
        start = time.time()
        attributes = get_attributes(fileName,verbosity)
        end = time.time() - start
        if verbosity: print("finished in ") + str(end) + (" seconds")
        insert_file(attributes,dbCreds,vault)
        fileInfo = lookup_file_by_path(fileName,dbCreds,vault)

    except Exception, e:
      print("error on " + fileName + " " + str(e))

  return

def lookup_file_by_path(path,dbCreds,vault):
  attributes = {}
  conn = mdb.connect(dbCreds['server'] , dbCreds['user'], dbCreds['password'], dbCreds['database'], cursorclass=MySQLdb.cursors.DictCursor);
  c = conn.cursor()
  c.execute('''SELECT * FROM {} where path = "%s" '''.format(vault) % ( path ) )
  data = c.fetchone()
  if data:
    for key in data.keys():
      attributes[key] = data[key]
  conn.close
  return attributes

我在这里做了一些根本错误的事情导致了竞争条件吗?或者还有其他我想念的东西。

谢谢, 托马斯C

1 个答案:

答案 0 :(得分:3)

您的代码中存在争用条件:

while True:
     if queue.empty() == True:
        return 
     fileName = queue.get()

首先,线程检查队列是否为空。如果不是,则尝试阻止get。但是,在调用queue.empty()queue.get之间的时间内,另一个线程可能已经从队列中消耗了最终项,这意味着get调用将永远阻塞。你应该这样做:

try:
    fileName = queue.get_nowait()
except Queue.Empty:
    return

如果这不能解决问题,你可以在线程方法中抛出一些print语句来确定它被卡住的确切位置,并从那里开始。但是,没有其他并发问题向我发出。

修改

顺便说一句,你在这里所做的事情可以更加干净地实施为ThreadPoolmultiprocessing.Pool

from multiprocessing.pool import ThreadPool
from functools import partial

def get_backup_list(dbCreds, verbosity, vault, fileName):
  backupFiles = []
  fileInfo = lookup_file_by_path(fileName,dbCreds,vault)
  ...

if __name__ == "__main__":
    pool = ThreadPool(6) # You could use a multiprocessing.Pool, too
    func = partial(get_backup_list, dbCreds, arguments.verbose, arguments.vault)
    pool.map(func, fileNames)
    pool.close()
    pool.join()

根据每个get_backup_list调用的工作量,您可能会发现它作为multiprocessing.Pool表现更好,因为它可以绕过全局解释器锁(GIL),这可以防止Python线程同时跨CPU核心执行。看起来你的代码可能是I / O绑定的,所以ThreadPool可能会很好。