没有队列处理大型数据集的Python多线程

时间:2013-12-12 01:05:18

标签: python multithreading

我正在运行大约800k行的csv文件。我需要一个穿过每一行的线程解决方案,并一次将32个线程生成一个worker。我想在没有队列的情况下这样做。看起来当前有队列的python线程解决方案正在耗费大量内存。

基本上想要读取一个csv文件行并放入一个工作线程。并且一次只需要运行32个线程。

这是当前的脚本。它似乎正在将整个csv文件读入队列并执行queue.join()。将整个csv加载到队列然后产生线程是否正确?

queue=Queue.Queue()

def worker():
    while True:
        task=queue.get()
        try:
            subprocess.call(['php {docRoot}/cli.php -u "api/email/ses" -r "{task}"'.format(
                docRoot=docRoot,
                task=task
            )],shell=True)
        except:
            pass
        with lock:
            stats['done']+=1
            if int(time.time())!=stats.get('now'):
                stats.update(
                    now=int(time.time()),
                    percent=(stats.get('done')/stats.get('total'))*100,
                    ps=(stats.get('done')/(time.time()-stats.get('start')))
                )
                print("\r    {percent:.1f}% [{progress:24}] {persec:.3f}/s ({done}/{total}) ETA {eta:<12}".format(
                    percent=stats.get('percent'),
                    progress=('='*int((23*stats.get('percent'))/100))+'>',
                    persec=stats.get('ps'),
                    done=int(stats.get('done')),
                    total=stats.get('total'),
                    eta=snippets.duration.time(int((stats.get('total')-stats.get('done'))/stats.get('ps')))
                ),end='')
           queue.task_done()


    for i in range(32):
        workers=threading.Thread(target=worker)
        workers.daemon=True
        workers.start()
    try:
        with open(csvFile,'rb') as fh:
        try:

                dialect=csv.Sniffer().sniff(fh.readline(),[',',';'])
            fh.seek(0)
            reader=csv.reader(fh,dialect)
            headers=reader.next()
        except csv.Error as e:
            print("\rERROR[CSV] {error}\n".format(error=e))
        else:
            while True:
            try:
                data=reader.next()
            except csv.Error as e:
                print("\rERROR[CSV] - Line {line}: {error}\n".format(                                       line=reader.line_num, error=e))
            except StopIteration:
                break
            else:
                stats['total']+=1
             queue.put(urllib.urlencode(dict(zip(headers,data)+dict(campaign=row.get('Campaign')).items())))
        queue.join()

4 个答案:

答案 0 :(得分:2)

除非你有一些可用的硬件,否则32个线程可能有点过分。

最佳线程数或进程数的经验法则是:(no. of cores * 2) - 1 在大多数硬件上都是7或15。

最简单的方法是启动7个线程,将每个线程作为参数传递“offset”。 即0到7之间的数字。

然后每个线程将跳过行,直到达到“偏移”数并处理该行。处理完行后,它可以跳过6行并处理第7行 - 重复直到不再有行。

此设置适用于线程和多个进程,并且在大多数计算机上的I / O非常高效,因为所有线程在任何给定时间都应该读取文件的大致相同部分。

我应该补充一点,这个方法特别适合python,因为每个线程一旦启动就或多或少是独立的,并且避免了其他方法常见的可怕的python全局锁。

答案 1 :(得分:1)

我不明白为什么你想要每行产生 32个线程。然而,数据处理是一个相当常见的令人难以置信的并行事情,可以通过Python的multiprocessing库轻松实现。

示例:

from multiprocessing import Pool

def job(args):
    # do some work

inputs = [...]  # define your inputs
Pool().map(job, inputs)

我留给您填写空白以满足您的特定要求

有关此pattenr的许多示例,请参阅:https://bitbucket.org/ccaih/ccav/src/tip/bin/

答案 2 :(得分:0)

你的问题很不清楚。您是否尝试初始化Queue以使其最大尺寸为64?

myq = Queue.Queue(maxsize=64)

然后,在.put()上尝试myq个新项目的生产者(一个或多个)将阻塞,直到消费者将队列大小减小到小于64.这将相应地限制所消耗的内存量。队列。默认情况下,队列是无限制的:如果生产者以比消费者更快的方式添加项目,则队列可以增长以消耗您拥有的所有RAM。

修改

  

这是当前的脚本。它似乎在阅读   将整个csv文件放入队列并执行queue.join()。是   它纠正了它将整个csv加载到队列中   然后产生线程?

缩进在你的帖子中搞砸了,所以不得不猜一些,但是:

  1. 在打开CSV文件之前,代码显然会启动32个线程。
  2. 您没有显示创建队列的代码。如上所述,如果它是Queue.Queue,默认情况下它是无限制的,并且可以增长到任何大小,如果你的主循环将项目放在它上面的速度比线程从中删除项目的速度快。由于您没有说明worker()所做的事情(或显示其代码),我们没有足够的信息来猜测是否是这种情况。但是,记忆的使用是不可能的建议就是这种情况。
  3. 并且,如前所述,您可以通过在创建队列时指定最大大小来轻松停止。
  4. 要获得更好的答案,请提供更好的信息; - )

    另一个编辑

    嗯,压痕仍然在斑点上混乱,但它更好。您是否尝试了任何建议?看起来你的工作线程每个都会产生一个新的进程,所以它们需要的时间比从csv文件中读取另一行所需的时间长得多。因此,您确实很可能将项目放在队列上的速度比它们被取消的速度快。所以,无数次;-), 尝试用(例如)maxsize=64初始化队列。然后揭示发生了什么。

    顺便说一句,except:中的worker()条款是一个非常糟糕的主意。如果出现任何问题,你永远不会知道。如果 忽略每个可能的异常(甚至包括KeyboardInterruptSystemExit),请至少记录异常信息。

    请注意@JamesAnderson所说的内容:除非你拥有非凡的硬件资源,否则尝试一次运行32个进程几乎肯定比运行不超过可用内核数量两倍的进程要慢。然后,这又取决于你的PHP程序的功能。例如,如果PHP程序大量使用磁盘I / O,那么任何多处理可能比没有任何处理慢。

答案 3 :(得分:0)

其他答案解释了如何使用Pool而无需管理队列(它为您管理它们),并且您不希望将进程数设置为32,而是将CPU数量设置为1。我会添加两件事。首先,您可能需要查看pandas包,它可以轻松地将您的csv文件导入Python。第二个是在其他答案中使用Pool的例子只传递一个带有单个参数的函数。不幸的是,您只能使用函数的所有输入传递Pool一个对象,这使得使用带有多个参数的函数变得很困难。这里的代码允许您使用pool调用具有多个参数的先前定义的函数:

import multiprocessing
from multiprocessing import Pool

def multiplyxy(x,y):

    return x*y



def funkytuple(t):
    """
    Breaks a tuple into a function to be called and a tuple
    of arguments for that function. Changes that new tuple into
    a series of arguments and passes those arguments to the
    function.
    """
    f = t[0]
    t = t[1]



    return f(*t)


def processparallel(func, arglist):
    """
    Takes a function and a list of arguments for that function
    and proccesses in parallel.
    """
    parallelarglist = []

    for entry in arglist:
        parallelarglist.append((func, tuple(entry)))

    cpu_count = int(multiprocessing.cpu_count() - 1)


    pool = Pool(processes = cpu_count)
    database = pool.map(funkytuple, parallelarglist)

    pool.close()
    return database

#Necessary on Windows
if __name__ == '__main__':
    x = [23, 23, 42, 3254, 32]
    y = [324, 234, 12, 425, 13]
    i = 0

    arglist = []
    while i < len(x):
        arglist.append([x[i],y[i]])
        i += 1


    database = processparallel(multiplyxy, arglist)

    print(database)