7 votos

¿Se puede ejecutar el multiprocesamiento con arcpy en una herramienta de secuencia de comandos?

He estado jugando alrededor con el módulo de multiprocesamiento para resolver algunos de procesamiento de datos en ArcMap 10.3.

He creado un script que funciona bien si lo ejecuto en modo INACTIVO. Veo a todos mis núcleos de max en el Administrador de Tareas y el código finaliza sin errores.

Ahora si me alambre esta secuencia de comandos como una Herramienta de secuencia de Comandos en ArcToolbox se tira un error extraño

No se pudo encontrar el archivo: de multiprocesamiento.la bifurcación de importación principales; main().mxd

Ahora leyendo varios hilos me veo regularmente que el script tiene que ser "ejecutar fuera del proceso". Así que sin marcar la casilla de verificación "ejecutar python en proceso" en el diálogo propiedades del script y se esperaba que el código se ejecute pero no, me sale un 000714 error y no he hecho nada en el código.

Así que mi pregunta es, simplemente, se puede crear una secuencia de comandos que utiliza el módulo de multiprocesamiento y ejecutarlo desde ArcToolbox? Desde mi limitado a jugar, parece que no y es una técnica que sólo se puede ejecutar desde la INACTIVIDAD?

ACTUALIZACIÓN

Por fin he conseguido que funcione, muchas gracias a todos los queridos ayuda. Decidí documentar completamente mi ejemplo sencillo y reunir alguno de los viaje ups tuve que superar en un documento en el ESRI GeoNet sitio web. Como Lucas la respuesta espero que esto proporciona un punto de partida para cualquier persona tratando de crear una secuencia de comandos de python herramienta que utiliza el multiprocesamiento. El documento se titula Crear una herramienta de secuencia de comandos que utiliza el multiprocesamiento.

11voto

Lucas Puntos 128

Sí, puede ejecutar el multiprocesamiento hijo procesos de una caja de herramientas de secuencia de comandos. A continuación se presenta el código de demostrar en una caja de herramientas de Python (*.pyt).

Hay una serie de "trampas". Algunos (pero no todos), que serán aplicables a Python herramientas de secuencia de comandos en un archivo binario (caja de herramientas*.tbx), pero yo sólo uso de cajas de Herramientas de Python en estos días así que no lo he probado.

Algunas "trampas"/consejos:

  • Asegúrese de que cada niño proceso tiene su propio espacio de trabajo (particularmente si se utiliza Spatial Analyst o cobertura de herramientas) para los archivos temporales y usar solo el principal proceso para asignar el trabajo, recoger los resultados y escribir los resultados finales. Esto es por lo que cualquier escrito a un conjunto final de datos se realiza por un proceso de evitar cualquier bloqueo o conflictos;
  • Sólo pasar pickleable objetos, tales como cadenas, listas, números;
  • Cualquiera de las funciones que desea que se ejecute como un proceso hijo DEBE estar en un importables módulo (con un nombre de archivo diferente a el .pyt), no el .pyt sí mismo. De lo contrario, obtendrás PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed;
  • Asegúrese de que el código que se ejecuta el multiprocesamiento en un importables módulo (con un nombre de archivo diferente a el .pyt), no el .pyt sí mismo. De lo contrario, obtendrás AssertionError: main_name not in sys.modules, main_name

Se aplica a *.py secuencias de comandos en un archivo binario caja de herramientas (*.tbx):

  • Asegúrese de que el código que analiza los parámetros de secuencia de comandos está protegido por una if __name__ == '__main__': bloque.
  • Usted puede mantener las funciones que desea llamar en el .py secuencia de comandos, pero es necesario importar la secuencia de comandos para sí mismo.

Ejemplo de código

Caja De Herramientas De Python

# test_multiprocessing.pyt
import os, sys
import multiprocessing
import arcpy

# Any functions you want to run as a child process MUST be in
# an importable module. A *.pyt is not importable by python
# Otherwise you'll get 
#     PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
# Also make sure the code that _does_ the multiprocessing is in an importable module
# Otherwise you'll get 
#     AssertionError: main_name not in sys.modules, main_name
from test_multiprocessing_functions import execute

class Toolbox(object):
    def __init__(self):
        '''Define toolbox properties (the toolbox name is the .pyt filename).'''
        self.label = 'Test Multiprocessing'
        self.alias = 'multiprocessing'

        # List of tool classes associated with this toolbox
        self.tools = [TestTool]

class TestTool(object):
    def __init__(self):

        self.label = 'Test Multiprocessing'
        self.description = 'Test Multiprocessing Tool'
        self.canRunInBackground = True
        self.showCommandWindow = False

    def isLicensed(self):
        return True

    def updateParameters(self, parameters):
        return

    def updateMessages(self, parameters):
        return

    def getParameterInfo(self):
        '''parameter definitions for GUI'''

        return [arcpy.Parameter(displayName='Input Rasters',
                                name='in_rasters',
                                datatype='DERasterDataset',
                                parameterType='Required',
                                direction='Input',
                                multiValue=True)]


    def execute(self, parameters, messages):
        # Make sure the code that _does_ the multiprocessing is in an importable module, not a .pyt
        # Otherwise you'll get 
        #     AssertionError: main_name not in sys.modules, main_name
        rasters = parameters[0].valueAsText.split(';')
        for raster in rasters:
            messages.addMessage(raster)

        execute(*rasters)

Importables Módulo de Python (que también puede ser utilizado como una herramienta de secuencia de comandos en un archivo binario (caja de herramientas.tbx)

#test_multiprocessing_functions.py
#  - Always run in foreground - unchecked
#  - Run Python script in process - checked

import os, sys, tempfile
import multiprocessing
import arcpy
from arcpy.sa import *

def execute(*rasters):

    for raster in rasters:
        arcpy.AddMessage(raster)

    #Set multiprocessing exe in case we're running as an embedded process, i.e ArcGIS
    #get_install_path() uses a registry query to figure out 64bit python exe if available
    multiprocessing.set_executable(os.path.join(get_install_path(), 'pythonw.exe'))

    #Create a pool of workers, keep one cpu free for surfing the net.
    #Let each worker process only handle 10 tasks before being restarted (in case of nasty memory leaks)
    pool = multiprocessing.Pool(processes=multiprocessing.cpu_count() - 1, maxtasksperchild=10)

    # Simplest multiprocessing is to map an iterable (i.e. a list of things to process) to a function
    # But this doesn't allow you to handle exceptions in a single process
    ##output_rasters = pool.map(worker_function, rasters)

    # Use apply_async instead so we can handle exceptions gracefully
    jobs={}
    for raster in rasters:
        jobs[raster]=pool.apply_async(worker_function, [raster]) # args are passed as a list
    for raster,result in jobs.iteritems():
        try:
            result = result.get()
            arcpy.AddMessage(result)
        except Exception as e:
            arcpy.AddWarning('{}\n{}'.format(raster, repr(e)))

    pool.close()
    pool.join()


def get_install_path():
    ''' Return 64bit python install path from registry (if installed and registered),
        otherwise fall back to current 32bit process install path.
    '''
    if sys.maxsize > 2**32: return sys.exec_prefix #We're running in a 64bit process

    #We're 32 bit so see if there's a 64bit install
    path = r'SOFTWARE\Python\PythonCore\2.7'

    from _winreg import OpenKey, QueryValue
    from _winreg import HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY

    try:
        with OpenKey(HKEY_LOCAL_MACHINE, path, 0, KEY_READ | KEY_WOW64_64KEY) as key:
            return QueryValue(key, "InstallPath").strip(os.sep) #We have a 64bit install, so return that.
    except: return sys.exec_prefix #No 64bit, so return 32bit path

def worker_function(in_raster):
    ''' Make sure you pass a filepath to raster, NOT an arcpy.sa.Raster object'''

    ## Example "real" work" (untested)
    ## Make a unique scratch workspace
    #scratch =  tempfile.mkdtemp()
    #out_raster = os.path.join(scratch, os.path.basename(in_raster))
    #arcpy.env.workspace = scratch
    #arcpy.env.scratchWorkspace=scratch
    #ras = Raster(in_raster)
    #result = Con(IsNull(ras), FocalStatistics(ras), ras)
    #result.save(out_raster)
    #del ras, result
    #return out_raster # leave calling script to clean up tempdir.
                       # could also pass out_raster in as a arg,
                       # but you'd have to ensure no other child processes
                       # are writing the that dir when the current
                       # child process is...

    # Do some "fake" work
    import time, random
    time.sleep(random.randint(0,20)/10.0) #sleep for a bit to simulate work
    return in_raster[::-1] #Return a reversed version of what was passed in


if __name__=='__main__':
    # import current script to avoid:
    #     PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed
    import test_multiprocessing_functions

    rasters = arcpy.GetParameterAsText(0).split(';')
    for raster in rasters:
        arcpy.AddMessage(raster)

    test_multiprocessing_functions.execute(*rasters)

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X