How to spawn parallel child processes on a multi-processor system?

前端 未结 4 1745
北海茫月
北海茫月 2020-12-04 06:52

I have a Python script that I want to use as a controller to another Python script. I have a server with 64 processors, so want to spawn up to 64 child processes of this sec

相关标签:
4条回答
  • 2020-12-04 07:25

    Here is the solution I came up, based on Nadia and Jim's comments. I am not sure if it is the best way, but it works. The original child script being called needs to be a shell script because I need to use some 3rd party apps including Matlab. So I had to take it out of Python and code it in bash.

    import sys
    import os
    import multiprocessing
    import subprocess
    
    def work(staname):
        print 'Processing station:',staname
        print 'Parent process:', os.getppid()
        print 'Process id:', os.getpid()
        cmd = [ "/bin/bash" "/path/to/executable/create_graphs.sh","--name=%s" % (staname) ]
        return subprocess.call(cmd, shell=False)
    
    if __name__ == '__main__':
    
        my_list = [ 'XYZ', 'ABC', 'NYU' ]
    
        my_list.sort()
    
        print my_list
    
        # Get the number of processors available
        num_processes = multiprocessing.cpu_count()
    
        threads = []
    
        len_stas = len(my_list)
    
        print "+++ Number of stations to process: %s" % (len_stas)
    
        # run until all the threads are done, and there is no data left
    
        for list_item in my_list:
    
            # if we aren't using all the processors AND there is still data left to
            # compute, then spawn another thread
    
            if( len(threads) < num_processes ):
    
                p = multiprocessing.Process(target=work,args=[list_item])
    
                p.start()
    
                print p, p.is_alive()
    
                threads.append(p)
    
            else:
    
                for thread in threads:
    
                    if not thread.is_alive():
    
                        threads.remove(thread)
    

    Does this seem like a reasonable solution? I tried to use Jim's while loop format, but my script just returned nothing. I am not sure why that would be. Here is the output when I run the script with Jim's 'while' loop replacing the 'for' loop:

    hostname{me}2% controller.py 
    ['ABC', 'NYU', 'XYZ']
    Number of processes: 64
    +++ Number of stations to process: 3
    hostname{me}3%
    

    When I run it with the 'for' loop, I get something more meaningful:

    hostname{me}6% controller.py 
    ['ABC', 'NYU', 'XYZ']
    Number of processes: 64
    +++ Number of stations to process: 3
    Processing station: ABC
    Parent process: 1056
    Process id: 1068
    Processing station: NYU
    Parent process: 1056
    Process id: 1069
    Processing station: XYZ
    Parent process: 1056
    Process id: 1071
    hostname{me}7%
    

    So this works, and I am happy. However, I still don't get why I can't use Jim's 'while' style loop instead of the 'for' loop I am using. Thanks for all the help - I am impressed with the breadth of knowledge @ stackoverflow.

    0 讨论(0)
  • 2020-12-04 07:28

    I don't think you need queue unless you intend to get data out of the applications (Which if you do want data, I think it may be easier to add it to a database anyway)

    but try this on for size:

    put the contents of your create_graphs.py script all into a function called "create_graphs"

    import threading
    from create_graphs import create_graphs
    
    num_processes = 64
    my_list = [ 'XYZ', 'ABC', 'NYU' ]
    
    threads = []
    
    # run until all the threads are done, and there is no data left
    while threads or my_list:
    
        # if we aren't using all the processors AND there is still data left to
        # compute, then spawn another thread
        if (len(threads) < num_processes) and my_list:
            t = threading.Thread(target=create_graphs, args=[ my_list.pop() ])
            t.setDaemon(True)
            t.start()
            threads.append(t)
    
        # in the case that we have the maximum number of threads check if any of them
        # are done. (also do this when we run out of data, until all the threads are done)
        else:
            for thread in threads:
                if not thread.isAlive():
                    threads.remove(thread)
    

    I know that this will result in 1 less threads than processors, which is probably good, it leaves a processor to manage the threads, disk i/o, and other things happening on the computer. If you decide you want to use the last core just add one to it

    edit: I think I may have misinterpreted the purpose of my_list. You do not need my_list to keep track of the threads at all (as they're all referenced by the items in the threads list). But this is a fine way of feeding the processes input - or even better: use a generator function ;)

    The purpose of my_list and threads

    my_list holds the data that you need to process in your function
    threads is just a list of the currently running threads

    the while loop does two things, start new threads to process the data, and check if any threads are done running.

    So as long as you have either (a) more data to process, or (b) threads that aren't finished running.... you want to program to continue running. Once both lists are empty they will evaluate to False and the while loop will exit

    0 讨论(0)
  • 2020-12-04 07:30

    What you are looking for is the process pool class in multiprocessing.

    import multiprocessing
    import subprocess
    
    def work(cmd):
        return subprocess.call(cmd, shell=False)
    
    if __name__ == '__main__':
        count = multiprocessing.cpu_count()
        pool = multiprocessing.Pool(processes=count)
        print pool.map(work, ['ls'] * count)
    

    And here is a calculation example to make it easier to understand. The following will divide 10000 tasks on N processes where N is the cpu count. Note that I'm passing None as the number of processes. This will cause the Pool class to use cpu_count for the number of processes (reference)

    import multiprocessing
    import subprocess
    
    def calculate(value):
        return value * 10
    
    if __name__ == '__main__':
        pool = multiprocessing.Pool(None)
        tasks = range(10000)
        results = []
        r = pool.map_async(calculate, tasks, callback=results.append)
        r.wait() # Wait on the results
        print results
    
    0 讨论(0)
  • 2020-12-04 07:35

    I would definitely use multiprocessing rather than rolling my own solution using subprocess.

    0 讨论(0)
提交回复
热议问题