Skip to content

Commit 9a7e5b1

Browse files
authored
bpo-35279: reduce default max_workers of ThreadPoolExecutor (GH-13618)
1 parent 293e9f8 commit 9a7e5b1

File tree

4 files changed

+22
-5
lines changed

4 files changed

+22
-5
lines changed

Doc/library/concurrent.futures.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ And::
159159
.. versionchanged:: 3.7
160160
Added the *initializer* and *initargs* arguments.
161161

162+
.. versionchanged:: 3.8
163+
Default value of *max_workers* is changed to ``min(32, os.cpu_count() + 4)``.
164+
This default value preserves at least 5 workers for I/O bound tasks.
165+
It utilizes at most 32 CPU cores for CPU bound tasks which release the GIL.
166+
And it avoids using very large resources implicitly on many-core machines.
167+
168+
ThreadPoolExecutor now reuses idle worker threads before starting
169+
*max_workers* worker threads too.
170+
162171

163172
.. _threadpoolexecutor-example:
164173

Lib/concurrent/futures/thread.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,14 @@ def __init__(self, max_workers=None, thread_name_prefix='',
129129
initargs: A tuple of arguments to pass to the initializer.
130130
"""
131131
if max_workers is None:
132-
# Use this number because ThreadPoolExecutor is often
133-
# used to overlap I/O instead of CPU work.
134-
max_workers = (os.cpu_count() or 1) * 5
132+
# ThreadPoolExecutor is often used to:
133+
# * CPU bound task which releases GIL
134+
# * I/O bound task (which releases GIL, of course)
135+
#
136+
# We use cpu_count + 4 for both types of tasks.
137+
# But we limit it to 32 to avoid consuming surprisingly large resource
138+
# on many core machine.
139+
max_workers = min(32, (os.cpu_count() or 1) + 4)
135140
if max_workers <= 0:
136141
raise ValueError("max_workers must be greater than 0")
137142

Lib/test/test_concurrent_futures.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,8 +755,8 @@ def record_finished(n):
755755

756756
def test_default_workers(self):
757757
executor = self.executor_type()
758-
self.assertEqual(executor._max_workers,
759-
(os.cpu_count() or 1) * 5)
758+
expected = min(32, (os.cpu_count() or 1) + 4)
759+
self.assertEqual(executor._max_workers, expected)
760760

761761
def test_saturation(self):
762762
executor = self.executor_type(4)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Change default *max_workers* of ``ThreadPoolExecutor`` from ``cpu_count() *
2+
5`` to ``min(32, cpu_count() + 4))``. Previous value was unreasonably
3+
large on many cores machines.

0 commit comments

Comments
 (0)