在python中进行线程处理 - 同时处理多个大文件

时间:2015-03-27 00:23:11

标签: python multithreading concurrency

我是python的新手,我无法理解线程是如何工作的。通过浏览文档,我的理解是在线程上调用join()是阻止它完成之前的阻塞方法。

为了给出一些背景知识,我有48个大型csv文件(多个GB),我试图解析这些文件以找出不一致的地方。线程没有共享状态。这可以在一段时间内以合理的时间单线程完成,但我试图将它作为练习同时进行。

这是文件处理的框架:

def process_file(data_file):
  with open(data_file) as f:
    print "Start processing {0}".format(data_file)
    line = f.readline()
    while line:
      # logic omitted for brevity; can post if required
      # pretty certain it works as expected, single 'thread' works fine
      line = f.readline()

  print "Finished processing file {0} with {1} errors".format(data_file, error_count)

def process_file_callable(data_file):
  try:
    process_file(data_file)
  except:
    print >> sys.stderr, "Error processing file {0}".format(data_file)

并发位:

def partition_list(l, n):
    """ Yield successive n-sized partitions from a list.
    """
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

partitions = list(partition_list(data_files, 4))
for partition in partitions:
  threads = []
  for data_file in partition:
    print "Processing file {0}".format(data_file)
    t = Thread(name=data_file, target=process_file_callable, args = (data_file,))
    threads.append(t)
    t.start()

  for t in threads:
    print "Joining {0}".format(t.getName())
    t.join(5)

  print "Joined the first chunk of {0}".format(map(lambda t: t.getName(), threads))

我将其作为:

运行
python -u datautils/cleaner.py > cleaner.out 2> cleaner.err

我的理解是join()应该阻止调用线程等待它调用的线程完成,但是我观察到的行为与我的期望不一致。

我从未在错误文件中看到错误,但我也从未在stdout上看到预期的日志消息。

父进程不会终止,除非我从shell中明确地终止它。如果我检查了Finished ...的打印数量,它永远不会是预期的48,但是介于12和15之间。但是,运行这个单线程,我可以确认多线程运行实际上正在处理所有内容并执行所有操作预期的验证,只是它似乎没有干净地终止。

我知道我一定做错了什么,但如果你能指出我正确的方向,我真的很感激。

2 个答案:

答案 0 :(得分:4)

我无法理解代码中的错误。但我可以建议你稍微重构一下。 首先,python中的线程根本不是并发的。它只是幻觉,因为有一个Global Interpreter Lock,所以只能同时执行一个线程。这就是我建议您使用multiprocessing module的原因:

from multiprocessing import Pool, cpu_count
pool = Pool(cpu_count)
for partition in partition_list(data_files, 4):
    res = pool.map(process_file_callable, partition)
    print res

第二,你使用的不是pythonic方式来读取文件:

with open(...) as f:
   line = f.readline()
    while line:
       ... # do(line)
      line = f.readline()

这是pythonic方式:

with open(...) as f:
    for line in f:
         ... # do(line)
  

这是内存效率高,速度快,并且可以实现简单的代码。 (c)PyDoc

顺便说一句,我只有一个假设,你的程序会以多线程方式发生什么 - 应用程序变得更慢,因为无序访问硬盘驱动器的速度明显慢于有序。如果您使用的是Linux,则可以尝试使用iostathtop来检查此假设。

如果你的应用程序没有完成工作,并且它在进程监视器中没有做任何事情(cpu或磁盘未激活),则意味着你有某种死锁或被阻止访问同一资源。

答案 1 :(得分:3)

感谢大家的意见和对不早回复的抱歉 - 我作为一个爱好项目开始和关闭这个。

我设法写了simple example,证明这是我的坏事:

from itertools import groupby
from threading import Thread
from random import randint
from time import sleep

for key, partition in groupby(range(1, 50), lambda k: k//10):
  threads = []
  for idx in list(partition):
    thread_name = 'thread-%d' % idx
    t = Thread(name=thread_name, target=sleep, args=(randint(1, 5),))
    threads.append(t)
    print 'Starting %s' % t.getName()
    t.start()

  for t in threads:
    print 'Joining %s' % t.getName()
    t.join()

  print 'Joined the first group of %s' % map(lambda t: t.getName(), threads)

最初失败的原因是while循环,为了简洁起见省略了逻辑'工作正常,但是输入的一些输入文件已损坏(有混乱的行),逻辑进入无限循环。这就是一些线程从未加入的原因。加入的超时确保它们都已启动,但有些从未完成,因此启动'加入'。另一个有趣的事实是腐败在最后一行,所以所有预期的数据都在处理中。

再次感谢您的建议 - 关于以while而不是pythonic方式处理文件的评论使我指向正确的方向,并且是的,线程按预期运行。

相关问题