Python3

时间:2016-05-25 19:00:28

标签: python json concurrency malloc urllib

我正在构建一个脚本,用于下载和解析奥巴马医疗保健交易所健康保险计划的福利信息。部分原因是需要从每个保险公司下载和解析计划权益JSON文件。为了做到这一点,我使用concurrent.futures.ThreadPoolExecutor和6个工作者来下载每个文件(使用urllib),通过JSON解析和循环并提取相关信息(存储在脚本中的嵌套字典中)。 / p>

(在win32上运行Python 3.5.1(v3.5.1:37a07cee5969,2015年12月6日,01:38:48)[MSC v.1900 32位(英特尔)]

问题在于,当我同时执行此操作时,脚本在通过JSON文件下载\ parsed \ looped之后似乎没有释放内存,过了一会儿,它崩溃了,malloc提升了内存错误。

当我按顺序执行时 - 使用简单的for in循环 - 然而,程序不会崩溃,也不需要占用大量内存。

def load_json_url(url, timeout):
    req = urllib.request.Request(url, headers={ 'User-Agent' : 'Mozilla/5.0' })
    resp = urllib.request.urlopen(req).read().decode('utf8')
    return json.loads(resp) 



 with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor:
        # Start the load operations and mark each future with its URL
        future_to_url = {executor.submit(load_json_url, url, 60): url for url in formulary_urls}
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                # The below timeout isn't raising the TimeoutError.
                data = future.result(timeout=0.01)
                for item in data:
                        if item['rxnorm_id']==drugid: 
                            for row in item['plans']:
                                print (row['drug_tier'])
                                (plansid_dict[row['plan_id']])['drug_tier']=row['drug_tier']
                                (plansid_dict[row['plan_id']])['prior_authorization']=row['prior_authorization']
                                (plansid_dict[row['plan_id']])['step_therapy']=row['step_therapy']
                                (plansid_dict[row['plan_id']])['quantity_limit']=row['quantity_limit']

            except Exception as exc:
                print('%r generated an exception: %s' % (url, exc))


            else:
                downloaded_plans=downloaded_plans+1

4 个答案:

答案 0 :(得分:4)

这不是你的错。 as_complete()在完成之前不会释放它的未来。已经存在问题:https://bugs.python.org/issue27144

目前,我认为大多数方法是将as_complete()包装在另一个循环中,该循环会根据您想要花费多少RAM以及您的结果有多大来计算到一个合理数量的期货。它会阻止每个块,直到所有工作都没有进入下一个块之前所以要慢一点或者可能在中间停留很长时间,但我现在看不到别的办法了,但是当有更聪明的时候会保留这个答案方式。

答案 1 :(得分:3)

作为替代解决方案,您可以在期货上致电add_done_callback,而根本不使用as_completed。关键是不要保留对期货的参考。因此,原始问题中的future_to_url列表是一个坏主意。

我所做的基本上是:

def do_stuff(future):
    res = future.result()  # handle exceptions here if you need to

f = executor.submit(...)
f.add_done_callback(do_stuff)

答案 2 :(得分:0)

如果您使用标准模块“ concurrent.futures”并要同时处理几百万个数据,那么一队工作人员将占用所有可用内存。

您可以使用 bounded-pool-executor https://github.com/mowshon/bounded_pool_executor

pip install bounded-pool-executor

示例:

from bounded_pool_executor import BoundedProcessPoolExecutor
from time import sleep
from random import randint

def do_job(num):
    sleep_sec = randint(1, 10)
    print('value: %d, sleep: %d sec.' % (num, sleep_sec))
    sleep(sleep_sec)

with BoundedProcessPoolExecutor(max_workers=5) as worker:
    for num in range(10000):
        print('#%d Worker initialization' % num)
        worker.submit(do_job, num)

答案 3 :(得分:0)

dodysw已正确指出,常见的解决方案是对输入进行分块并将任务大块提交给执行者。他还正确地指出,在开始处理下一个块之前,等待每个块被完全处理会损失一些性能。

我建议一种更好的解决方案,该方案将连续的任务流提供给执行程序,同时对最大并行任务数施加上限,以保持较低的内存占用。

技巧是使用concurrent.futures.wait来跟踪已完成的期货和仍在等待完成的期货:

def load_json_url(url):
    try:
        req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        resp = urllib.request.urlopen(req).read().decode('utf8')
        return json.loads(resp), None
    except Exception as e:
        return url, e

MAX_WORKERS = 6
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
    futures_done = set()
    futures_notdone = set()
    for url in formulary_urls:
        futures_notdone.add(executor.submit(load_json_url, url))

        if len(futures_notdone) >= MAX_WORKERS:
            done, futures_notdone = concurrent.futures.wait(futures_notdone, return_when=concurrent.futures.FIRST_COMPLETED)
            futures_done.update(done)

# Process results.
downloaded_plans = 0
for future in futures_done:
    json, exc = future.result()
    if exc:
        print('%r generated an exception: %s' % (json, exc))
    else:
        downloaded_plans += 1
        for item in data:
            if item['rxnorm_id'] == drugid:
                for row in item['plans']:
                    print(row['drug_tier'])
                    (plansid_dict[row['plan_id']])['drug_tier'] = row['drug_tier']
                    (plansid_dict[row['plan_id']])['prior_authorization'] = row['prior_authorization']
                    (plansid_dict[row['plan_id']])['step_therapy'] = row['step_therapy']
                    (plansid_dict[row['plan_id']])['quantity_limit'] = row['quantity_limit']

当然,您也可以定期在循环内处理结果,以不时清空futures_done。例如,每次futures_done中的项目数超过1000(或任何其他满足您需要的数量)时,您都可以这样做。如果您的数据集非常大,那么这样做可能会派上用场,而仅靠结果会导致大量内存使用。