0

If I call apply_async 10,000 times, assuming the OOM-killer doesn't interfere, will multiprocessing start them all simultaneously, or will it start them in batches. For example.. Every 100 starts, waiting for 90 to finish starting before starting any more?

Dustin

Dustin Oprea
  • 9,673
  • 13
  • 65
  • 105

1 Answers1

6

apply_async() is a method of multiprocessing.Pool objects, and delivers all work to the number of processes you specified when you created the Pool. Only that many tasks can run simultaneously. The rest are saved in queues (or pipes) by the multiprocessing machinery, and automatically doled out to processes as they complete tasks already assigned. Much the same is true of all the Pool methods to which you feed multiple work items.

A little more clarification: apply_async doesn't create, or start, any processes. The processes were created when you called Pool(). The processes just sit there and wait until you invoke Pool methods (like apply_async()) that ask for some real work to be done.

Example

Play with this:

MAX = 100000

from time import sleep
def f(i):
    sleep(0.01)
    return i

def summer(summand):
    global SUM, FINISHED
    SUM += summand
    FINISHED += 1

if __name__ == "__main__":
    import multiprocessing as mp
    SUM = 0
    FINISHED = 0
    pool = mp.Pool(4)

    print "queuing", MAX, "work descriptions"
    for i in xrange(MAX):
        pool.apply_async(f, args=(i,), callback=summer)
        if i % 1000 == 0:
            print "{}/{}".format(FINISHED, i),
    print

    print "closing pool"
    pool.close()

    print "waiting for processes to end"
    pool.join()

    print "verifying result"
    print "got", SUM, "expected", sum(xrange(MAX))

Output is like:

queuing 100000 work descriptions
0/0 12/1000 21/2000 33/3000 42/4000
... stuff chopped for brevity ...
1433/95000 1445/96000 1456/97000 1466/98000 1478/99000
closing pool
waiting for processes to end
... and it waits here "for a long time" ...
verifying result
got 4999950000 expected 4999950000

You can answer most of your questions just by observing its behavior. The work items are queued up quickly. By the time we see "closing pool", all the work items have been queued, but 1478 have already completed, and about 98000 are still waiting for some process to work on them.

If you take the sleep(0.01) out of f(), it's much less revealing, because results come back almost as fast as work items are queued.

Memory use remains trivial no matter how you run it, though. The work items here (the name of the function ("f") and its pickled integer argument) are tiny.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
  • More specifically, I'm asking that, if I call apply_async N times (where N is large), will the work be assigned as fast as possible or throttled in some way? – Dustin Oprea Nov 17 '13 at 06:38
  • And here I thought I answered that ;-) What's unclear? The work will be assigned as fast as possible, *and* it's throttled by how fast the processes can compute results. How could it possibly be otherwise? If you have `P` processes, the first `P` async calls will *begin* as fast as possible. No others will start until one of those first `P` async calls completes. Then one of the remaining `N` async calls will be given to the process that completed its first task. Etc etc etc. I think your mental model may be *way* over-complicated. It's really quite simple :-) – Tim Peters Nov 17 '13 at 07:00
  • I'm just asking about the starting aspect. You mentioned that you believe the pool "delivers all work to the number of processes you specified when you created the Pool", but still left room for throttling. – Dustin Oprea Nov 17 '13 at 07:51
  • I'm sorry, I guess I really don't know what you mean by "starting" - or by "throttling" ;-) The names of the `N` functions, and pickles of their arguments (if any), are put on queues (or fed to pipes) immediately. That's all done by the main program. So *some* of the work is done immediately for all `N` - but a tiny part, all concerned with implementation overheads. The `P` processes pull that info off the queues/pipes, each process one task at a time, and do the real work one task at a time. Is that "throttling"? Not to me, but maybe to you. – Tim Peters Nov 17 '13 at 16:34
  • @DustinOprea, see my edit just now for code you can play with. – Tim Peters Nov 17 '13 at 18:10