如何在Python中使用多个但数量有限的线程来处理列表

如何在Python中使用多个但数量有限的线程来处理列表,python,multithreading,pandas,arcgis,Python,Multithreading,Pandas,Arcgis,我有一个数据框,有几千行长,其中一列包含两对GPS坐标,我试图用它来计算这些坐标之间的驾驶时间。我有一个函数,它接受这些坐标并返回驾驶时间,计算每个条目可能需要3-8秒。因此,整个过程可能需要相当长的时间。我希望能够做到的是:使用3-5个线程,遍历列表并计算驱动器时间,然后继续下一个条目,而其他线程正在完成,并且在此过程中创建的线程不超过5个。独立地,我让一切都正常工作——我可以运行多个线程,我可以跟踪线程数,直到允许的最大线程数下降到限制以下,直到下一次启动,我可以迭代数据帧并计算驱动器时间。

我有一个数据框,有几千行长,其中一列包含两对GPS坐标,我试图用它来计算这些坐标之间的驾驶时间。我有一个函数,它接受这些坐标并返回驾驶时间,计算每个条目可能需要3-8秒。因此,整个过程可能需要相当长的时间。我希望能够做到的是:使用3-5个线程,遍历列表并计算驱动器时间,然后继续下一个条目,而其他线程正在完成,并且在此过程中创建的线程不超过5个。独立地,我让一切都正常工作——我可以运行多个线程,我可以跟踪线程数,直到允许的最大线程数下降到限制以下,直到下一次启动,我可以迭代数据帧并计算驱动器时间。然而,我很难把它们拼在一起。这是我所拥有的经过编辑、精简的版本

import pandas
import threading
import arcgis

class MassFunction:
    #This is intended to keep track of the active threads
    MassFunction.threadCount = 0

    def startThread(functionName,params=None):
        #This kicks off a new thread and should count up to keep track of the threads
        MassFunction.threadCount +=1

        if params is None:
            t = threading.Thread(target=functionName)
        else:
            t = threading.Thread(target=functionName,args=[params])
        t.daemon = True
        t.start()

class GeoAnalysis:
    #This class handles the connection to the ArcGIS services
    def __init__(self):
        super(GeoAnalysis, self).__init__()
        self.my_gis = arcgis.gis.GIS("https://www.arcgis.com", username, pw)

    def drivetimeCalc(self, coordsString):
        #The coords come in as a string, formatted as 'lat_1,long_1,lat_2,long_2'
        #This is the bottleneck of the process, as this calculation/response
        #below takes a few seconds to get a response
        points = coordsString.split(", ")
        route_service_url = self.my_gis.properties.helperServices.route.url
        self.route_layer = arcgis.network.RouteLayer(route_service_url, gis=self.my_gis)
        point_a_to_point_b = "{0}, {1}; {2}, {3}".format(points[1], points[0], points[3], points[2])
        result = self.route_layer.solve(stops=point_a_to_point_b,return_directions=False, return_routes=True,output_lines='esriNAOutputLineNone',return_barriers=False, return_polygon_barriers=False,return_polyline_barriers=False)
        travel_time = result['routes']['features'][0]['attributes']['Total_TravelTime']
        #This is intended to 'remove' one of the active threads 
        MassFunction.threadCount -=1
        return travel_time


class MainFunction:
    #This is to give access to the GeoAnalysis class from this class
    GA = GeoAnalysis()

    def closureDriveTimeCalc(self,coordsList):
        #This is intended to loop in the event that a fifth loop gets started and will prevent additional threads from starting
        while MassFunction.threadCount > 4:
            pass
        MassFunction.startThread(MainFunction.GA.drivetimeCalc,coordsList)

    def driveTimeAnalysis(self,location):
        #This reads a csv file containing a few thousand entries. 
        #Each entry/row contains gps coordinates, which need to be 
        #iterated over to calculate the drivetimes
        locationMemberFile = pandas.read_csv(someFileName)
        #The built-in apply() method in pandas seems to be the
        #fastest way to iterate through the rows

        locationMemberFile['DRIVETIME'] = locationMemberFile['COORDS_COL'].apply(self.closureDriveTimeCalc)
当我现在使用VS代码运行这个程序时,我可以看到调用堆栈中的线程计数上升到数千个,所以我感觉它不是在等待线程完成并对threadCount值进行加减。如有任何想法/建议/提示,将不胜感激


编辑:本质上,我的问题是如何获取旅行时间值,以便将其放入数据帧中。我目前没有closureDriveTimeCalc函数的return语句,因此当函数正确运行时,它不会将任何信息发送回apply()方法。

而不是在apply中执行此操作,我会使用:


与其在应用程序中执行此操作,不如使用:


你在用CPython吗?因为你的线程可能会受到GIL的阻碍,因为这看起来像是CPU边界,在这些方面我没有做任何特别的事情。我在测试过程中使用升华或VS代码,然后在情况稳定时编译成exe。这个例子是一个大得多的程序中的一个小片段。我不确定这是否回答了你的问题。如果没有,我可以尝试澄清。好的,nvm,我意识到你传递给线程的函数是I/O绑定的,所以这不会受到你使用的CPython的影响?因为你的线程可能会受到GIL的阻碍,因为这看起来像是CPU边界,在这些方面我没有做任何特别的事情。我在测试过程中使用升华或VS代码,然后在情况稳定时编译成exe。这个例子是一个大得多的程序中的一个小片段。我不确定这是否回答了你的问题。如果没有,我可以尝试澄清。好的,nvm,我意识到你传递给线程的函数是I/O绑定的,所以这不会受到GIL的影响。谢谢@Andy Hayden的输入。我尝试了一下你的建议,但不得不做一些修改。因为我需要closureDriveTimeCalc函数的输出,所以我稍微修改了代码,由此产生了一个问题和一个问题。这种多线程处理似乎比多线程处理慢得多,返回值“加倍”。因此,如果我循环1000组坐标来计算驱动时间,它会返回2000个结果,而其他所有值都是零。这很奇怪,我还不确定它为什么会这样做。@NLee23您也可以使用multiprocessing.pool import ThreadPool中的
,类似地。如果任务是IO绑定的,而不是CPU绑定的,这可能会更快。它是IO绑定的,所以也许我应该试试。谢谢你的建议!谢谢你的输入@Andy Hayden我尝试了一下你的建议,但不得不做一些修改。因为我需要closureDriveTimeCalc函数的输出,所以我稍微修改了代码,由此产生了一个问题和一个问题。这种多线程处理似乎比多线程处理慢得多,返回值“加倍”。因此,如果我循环1000组坐标来计算驱动时间,它会返回2000个结果,而其他所有值都是零。这很奇怪,我还不确定它为什么会这样做。@NLee23您也可以使用multiprocessing.pool import ThreadPool中的
,类似地。如果任务是IO绑定的,而不是CPU绑定的,这可能会更快。它是IO绑定的,所以也许我应该试试。谢谢你的建议!
from multiprocessing import Pool

with Pool(processes=4) as pool:
    locationMemberFile['DRIVETIME'] = pool.map(self.closureDriveTimeCalc, locationMemberFile['COORDS_COL']))