用线程替换foreach

时间:2018-04-06 10:15:14

标签: python multithreading

我的程序基本上必须从数据库中获取大约6000个项目,并为每个项目调用外部API。这几乎需要30分钟才能完成。我只是想在这里使用线程,我可以创建多线程并分割进程并减少时间。所以我提出了这样的事情。但我在这里有两个问题。如何存储函数处理的API的响应。

api = externalAPI()
for x in instruments:
    response = api.getProcessedItems(x.symbol, days, return_perc);
    if(response > float(return_perc)):
         return_response.append([x.trading_symbol, x.name, response])

所以在上面的例子中,for循环运行了6000次(len(instruments) == 6000

现在让我把6000件物品分成2 * 3000件物品并做这样的事情

class externalApi:

 def handleThread(self, symbol, days, perc):
    //I call the external API and process the items 
    // how do i store the processed data

 def getProcessedItems(self,symbol, days, perc):
    _thread.start_new_thread(self.handleThread, (symbol, days, perc))
    _thread.start_new_thread(self.handleThread, (symbol, days, perc))
    return self.thread_response

我刚刚开始使用线程。如果我知道这是减少时间的正确方法,那将会很有帮助。

P.S:时间在这里很重要。我想从30分钟缩短到1分钟。

2 个答案:

答案 0 :(得分:0)

我建议使用worker-queue模式,如此......

你将拥有一个工作队列,每个工作人员将接受一份工作并对其进行处理,结果将放在另一个队列中,当所有工人完成时,将读取结果队列并处理结果

def worker(pool, result_q):
  while True:
    job = pool.get()
    result = handle(job) #handle job
    result_q.put(result)
    pool.task_done()



q = Queue.Queue()
res_q = Queue.Queue()
for i in range(num_worker_threads):
  t = threading.Thread(target=worker, args=(q, res_q))
  t.setDaemon(True)
  t.start()

for job in jobs:
  q.put(job)

q.join()       

while not res_q.empty():
   result = res_q.get()
   # do smth with result

答案 1 :(得分:0)

shahaf's answer中建议的工作队列模式工作正常,但Python在concurret.futures中提供更高级别的抽象。即ThreadPoolExecutor,它将负责为您排队和启动线程:

from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(max_workers=30)
responses = executor.map(process_item, (x.symbol for x in instruments))

使用excutor.map()的主要复杂因素是它只能映射一个参数,这意味着proces_item只能有一个输入,即symbol)。

但是,如果需要更多参数,则可以定义一个新函数,该函数将固定除一个参数之外的所有参数。这可以手动完成,也可以使用partial中的特殊Python functools调用完成:

from functools import partial
process_item = partial(api.handleThread, days=days, perc=return_perc)

ThreadPoolExecutor策略应用于您当前的问题,将有一个类似于以下的解决方案:

from concurrent.futures import ThreadPoolExecutor
from functools import partial

class Instrument:
    def __init__(self, symbol, name):
        self.symbol = symbol
        self.name = name

instruments = [Instrument('SMB', 'Name'), Instrument('FNK', 'Funky')]

class externalApi:
    def handleThread(self, symbol, days, perc):
        # Call the external API and process the items 
        # Example, to give something back:
        if symbol == 'FNK':
            return days*3
        else:
            return days

def process_item_generator(api, days, perc):
    return partial(api.handleThread, days=days, perc=perc)

days = 5
return_perc = 10
api = externalApi()

process_item = process_item_generator(api, days, return_perc)

executor = ThreadPoolExecutor(max_workers=30)
responses = executor.map(process_item, (x.symbol for x in instruments))

return_response = ([x.symbol, x.name, response]
                   for x, response in zip(instruments, responses)
                   if response > float(return_perc))

这里我假设x.symbolx.trading_symbol相同,我已经对你的API调用进行了虚拟实现,以获得某种类型的返回值,但它应该给出一个很好的想法这该怎么做。因此,代码有点长,但是再一次,它变成了一个可运行的例子。