diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst index 5d9a7b6314b1668..af50fe1bf03cffc 100644 --- a/Doc/library/threading.rst +++ b/Doc/library/threading.rst @@ -510,7 +510,7 @@ since it is impossible to detect the termination of alien threads. .. class:: Thread(group=None, target=None, name=None, args=(), kwargs={}, *, \ - daemon=None, context=None) + daemon=None, context=None, start=False) This constructor should always be called with keyword arguments. Arguments are: @@ -545,6 +545,8 @@ since it is impossible to detect the termination of alien threads. current context, pass the value from :func:`~contextvars.copy_context`. The flag defaults true on free-threaded builds and false otherwise. + If *start* is true, start immediately the thread after initialization. + If the subclass overrides the constructor, it must make sure to invoke the base class constructor (``Thread.__init__()``) before doing anything else to the thread. @@ -558,6 +560,9 @@ since it is impossible to detect the termination of alien threads. .. versionchanged:: 3.14 Added the *context* parameter. + .. versionchanged:: next + Added the *start* parameter. + .. method:: start() Start the thread's activity. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 210bcafe65f5e9c..4fca2414851f9fe 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -461,6 +461,13 @@ symtable (Contributed by Serhiy Storchaka in :gh:`153844`.) +threading +--------- + +* Add *start* parameter to :class:`threading.Thread` to start immediately the + thread after initialization + (Contributed by Victor Stinner in :gh:`155334`.) + tkinter ------- diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 909359b648709fe..2d22624002686ff 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -233,8 +233,8 @@ def weakref_cb(_, q=self._work_queue): t = threading.Thread(name=thread_name, target=_worker, args=(weakref.ref(self, weakref_cb), self._create_worker_context(), - self._work_queue)) - t.start() + self._work_queue), + start=True) self._threads.add(t) _threads_queues[t] = self._work_queue diff --git a/Lib/concurrent/interpreters/__init__.py b/Lib/concurrent/interpreters/__init__.py index ea4147ee9a25da5..b1736eb88e5bd1e 100644 --- a/Lib/concurrent/interpreters/__init__.py +++ b/Lib/concurrent/interpreters/__init__.py @@ -242,6 +242,6 @@ def call_in_thread(self, callable, /, *args, **kwargs): The return value and any raised exception are discarded. """ - t = threading.Thread(target=self._call, args=(callable, args, kwargs)) - t.start() - return t + return threading.Thread(target=self._call, + args=(callable, args, kwargs), + start=True) diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index ef3d014d936ce85..a5a2c271f73778d 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -543,7 +543,7 @@ def __request_interrupt(self): self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) def interrupt_subprocess(self): - threading.Thread(target=self.__request_interrupt).start() + threading.Thread(target=self.__request_interrupt, start=True) def kill_subprocess(self): if self._afterid is not None: diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 802f0248e4e4594..d2fdc0bd2ad5aca 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -158,7 +158,7 @@ def main(del_exitfunc=False): name='SockThread', args=((LOCALHOST, port),), daemon=True, - ).start() + start=True) while True: try: diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index 11c6e21de90e693..2bf4b95de2b6e0d 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -172,9 +172,8 @@ def serve_forever(self): self.stop_event = threading.Event() process.current_process()._manager_server = self try: - accepter = threading.Thread(target=self.accepter) - accepter.daemon = True - accepter.start() + accepter = threading.Thread(target=self.accepter, + daemon=True, start=True) try: while not self.stop_event.is_set(): self.stop_event.wait(1) @@ -193,9 +192,8 @@ def accepter(self): c = self.listener.accept() except OSError: continue - t = threading.Thread(target=self.handle_request, args=(c,)) - t.daemon = True - t.start() + threading.Thread(target=self.handle_request, args=(c,), + daemon=True, start=True) def _handle_request(self, c): request = None diff --git a/Lib/multiprocessing/resource_sharer.py b/Lib/multiprocessing/resource_sharer.py index b8afb0fbed3a3c2..ec0af63e6a0ab8d 100644 --- a/Lib/multiprocessing/resource_sharer.py +++ b/Lib/multiprocessing/resource_sharer.py @@ -125,9 +125,7 @@ def _start(self): util.debug('starting listener and thread for sending handles') self._listener = Listener(authkey=process.current_process().authkey, backlog=128) self._address = self._listener.address - t = threading.Thread(target=self._serve) - t.daemon = True - t.start() + t = threading.Thread(target=self._serve, daemon=True, start=True) self._thread = t def _serve(self): diff --git a/Lib/profiling/sampling/_child_monitor.py b/Lib/profiling/sampling/_child_monitor.py index ec56f75719f9d17..8527ef4752d243b 100644 --- a/Lib/profiling/sampling/_child_monitor.py +++ b/Lib/profiling/sampling/_child_monitor.py @@ -88,8 +88,8 @@ def __enter__(self): target=self._monitor_loop, daemon=True, name=f"child-monitor-{self.parent_pid}", + start=True, ) - self._monitor_thread.start() return self def __exit__(self, exc_type, exc_val, exc_tb): diff --git a/Lib/profiling/sampling/gecko_collector.py b/Lib/profiling/sampling/gecko_collector.py index 2de8cce387e7f26..32b076607f8fa80 100644 --- a/Lib/profiling/sampling/gecko_collector.py +++ b/Lib/profiling/sampling/gecko_collector.py @@ -749,8 +749,7 @@ def spin(): sys.stderr.write('\r' + ' ' * (len(message) + 3) + '\r') sys.stderr.flush() - spinner_thread = threading.Thread(target=spin, daemon=True) - spinner_thread.start() + spinner_thread = threading.Thread(target=spin, daemon=True, start=True) temp_path = None replaced = False diff --git a/Lib/subprocess.py b/Lib/subprocess.py index a14fede00c391c9..e60df2cc218e3b8 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1792,16 +1792,14 @@ def _communicate(self, input, endtime, orig_timeout): self._stdout_buff = [] self.stdout_thread = \ threading.Thread(target=self._readerthread, - args=(self.stdout, self._stdout_buff)) - self.stdout_thread.daemon = True - self.stdout_thread.start() + args=(self.stdout, self._stdout_buff), + daemon=True, start=True) if self.stderr and not hasattr(self, "_stderr_buff"): self._stderr_buff = [] self.stderr_thread = \ threading.Thread(target=self._readerthread, - args=(self.stderr, self._stderr_buff)) - self.stderr_thread.daemon = True - self.stderr_thread.start() + args=(self.stderr, self._stderr_buff), + daemon=True, start=True) # Start writer thread to send input to stdin, unless already # started. The thread writes input and closes stdin when done, @@ -1809,9 +1807,8 @@ def _communicate(self, input, endtime, orig_timeout): if self.stdin and not hasattr(self, "_stdin_thread"): self._stdin_thread = \ threading.Thread(target=self._writerthread, - args=(input,)) - self._stdin_thread.daemon = True - self._stdin_thread.start() + args=(input,), + daemon=True, start=True) # Wait for the writer thread, or time out. If we time out, the # thread remains writing and the fd left open in case the user diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 96b43936be92cda..66b08aded9c7404 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1519,6 +1519,15 @@ def run_in_bg(): self.assertEqual(err, b"") self.assertEqual(out.strip(), b"Exiting...") + def test_threading_start(self): + # Test start=True parameter of Thread + def noop(): + pass + + t = threading.Thread(target=noop, start=True) + t.join() + + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): diff --git a/Lib/threading.py b/Lib/threading.py index abac31e25886fae..2eec4502f1f2a60 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1014,7 +1014,7 @@ class Thread: _initialized = False def __init__(self, group=None, target=None, name=None, - args=(), kwargs=None, *, daemon=None, context=None): + args=(), kwargs=None, *, daemon=None, context=None, start=False): """This constructor should always be called with keyword arguments. Arguments are: *group* should be None; reserved for future extension when a ThreadGroup @@ -1039,6 +1039,8 @@ class is implemented. contextvars.Context(). To explicitly start with a copy of the current context, pass the value from contextvars.copy_context(). + If *start* is true, start immediately the thread after initialization. + If a subclass overrides the constructor, it must make sure to invoke the base class constructor (Thread.__init__()) before doing anything else to the thread. @@ -1081,6 +1083,9 @@ class is implemented. # For debugging and _after_fork() _dangling.add(self) + if start: + self.start() + def _after_fork(self, new_ident=None): # Private! Called by threading._after_fork(). self._started._at_fork_reinit() diff --git a/Misc/NEWS.d/next/Library/2026-08-07-14-56-54.gh-issue-155334.-W17y0.rst b/Misc/NEWS.d/next/Library/2026-08-07-14-56-54.gh-issue-155334.-W17y0.rst new file mode 100644 index 000000000000000..61b4f2f0c518d5e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-07-14-56-54.gh-issue-155334.-W17y0.rst @@ -0,0 +1,2 @@ +Add *start* parameter to :class:`threading.Thread` to start immediately the +thread after initialization. Patch by Victor Stinner.