executor.map hangs at seemly random iteration when iterable is large #114948
Copy link
Copy link
Closed as not planned
Closed as not planned
Copy link
Labels
pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is providedperformancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-multiprocessing
Description
Bug report
Bug description:
Minimum working example of issue:
import numpy as np
import concurrent.futures
with concurrent.futures.ProcessPoolExecutor() as executor:
executor.map(print, np.arange(1000000))Each time this is run it seems to hang at a different iteration with no apparent CPU or memory usage. May be related to #74028
CPython versions tested on:
3.9
Operating systems tested on:
Linux
Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is providedperformancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-multiprocessing
Activity
gaogaotiantian commentedon Feb 3, 2024
Is this reproducible without
numpy?np.arangeis simple enough but it would be nice to rule out all the possible externel dependencies in the example.tburke02 commentedon Feb 3, 2024
Yes, same behavior when using
range.terryjreedy commentedon Feb 3, 2024
Win10, main, Command Prompt, with
range: I get multiple duplicate tracebacks.With the needed (on Windows) clause added, it prints 0 to 12 on separate lines and hangs. ^C and I get 12 interlaced tracebacks with some of the following lines
Jason-Y-Z commentedon Feb 3, 2024
Thanks all for the discussion and feedback. I've started a PR which should hopefully address/help with this and a couple of related issues.
gaogaotiantian commentedon Feb 3, 2024
This does not hang infinitely, it just takes a long time. (~1min on my comupter).
This is the main process timeline for a "normal"
mapoperation, where the input isrange(100).The MainThread is doing the
map, whichsubmitthe work item one by one to a dict. In eachsubmit, two thing happen:wakeupsignal is sent toself.thread_wakeupqueueWorkItem's id is added toself._work_idsqueueDuring the first call of
submit, an executor manager thread is brought up (Thread-1in the graph), which is in charge of actually distributing the tasks.It will get the
WorkItems fromself._work_idsandputthem in a process queue that has a size ofMAX_WORKERS + 1for the process workers. Because the queue has a maximum size, the manager thread will not be able to put all the tasks in it.When the task queue is full, the manager waits for a broken or wakeup signal in
wait_result_broken_or_wakeup, and this is where the issue happened.In this function, it needs to read all the pending
wakeupsignals inself.thread_wakeupqueue mentioned above. Remember, thesubmitwill not be blocked, it's not lazily evaluated, so each task sends a wakeup signal. This function needs to process ALL of them to be able to send moreWorkItemsto the worker process.Each
poll&recvtakes about 50 us on my computer:So if you have 1e6 tasks like in the example, you'll need 50s to process all the wake up signals before you can send out more
WorkItems to the process workers.So it's not a real dead lock, it just takes a long time. However, I do agree that if we can do something about it, that would be nice. But it won't make this go any faster even with lazy evaluation - it will make this smoother, as each wakeup signal always need to take 50us and it needs to be processed anyway (but it can be parallelized so if the task itself takes more than that, it should be ignorable if it's not blocking the task distribution).
Jason-Y-Z commentedon Feb 6, 2024
@gaogaotiantian Thanks for the profiling, please see #114975 for an attempt to improve the large iterator use case
gaogaotiantian commentedon Feb 7, 2024
Ah okay this is a false alarm. What you should do is to specify a
chunksizeto batchify you data.The docs says:
should just solve the issue.