Skip to content

Commit 58b5c5a

Browse files
committed
Issue #18882: Add threading.main_thread() function.
1 parent 3c56145 commit 58b5c5a

4 files changed

Lines changed: 102 additions & 23 deletions

File tree

Doc/library/threading.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ This module defines the following functions:
5757
and threads that have not yet been started.
5858

5959

60+
.. function:: main_thread()
61+
62+
Return the main :class:`Thread` object. In normal conditions, the
63+
main thread is the thread from which the Python interpreter was
64+
started.
65+
66+
.. versionadded:: 3.4
67+
68+
6069
.. function:: settrace(func)
6170

6271
.. index:: single: trace function

Lib/test/test_threading.py

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121

2222
from test import lock_tests
2323

24+
25+
# Between fork() and exec(), only async-safe functions are allowed (issues
26+
# #12316 and #11870), and fork() from a worker thread is known to trigger
27+
# problems with some operating systems (issue #3863): skip problematic tests
28+
# on platforms known to behave badly.
29+
platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5',
30+
'hp-ux11')
31+
32+
2433
# A trivial mutable counter.
2534
class Counter(object):
2635
def __init__(self):
@@ -468,15 +477,70 @@ def test_is_alive_after_fork(self):
468477
pid, status = os.waitpid(pid, 0)
469478
self.assertEqual(0, status)
470479

480+
def test_main_thread(self):
481+
main = threading.main_thread()
482+
self.assertEqual(main.name, 'MainThread')
483+
self.assertEqual(main.ident, threading.current_thread().ident)
484+
self.assertEqual(main.ident, threading.get_ident())
471485

472-
class ThreadJoinOnShutdown(BaseTestCase):
486+
def f():
487+
self.assertNotEqual(threading.main_thread().ident,
488+
threading.current_thread().ident)
489+
th = threading.Thread(target=f)
490+
th.start()
491+
th.join()
492+
493+
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
494+
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
495+
def test_main_thread_after_fork(self):
496+
code = """if 1:
497+
import os, threading
498+
499+
pid = os.fork()
500+
if pid == 0:
501+
main = threading.main_thread()
502+
print(main.name)
503+
print(main.ident == threading.current_thread().ident)
504+
print(main.ident == threading.get_ident())
505+
else:
506+
os.waitpid(pid, 0)
507+
"""
508+
_, out, err = assert_python_ok("-c", code)
509+
data = out.decode().replace('\r', '')
510+
self.assertEqual(err, b"")
511+
self.assertEqual(data, "MainThread\nTrue\nTrue\n")
512+
513+
@unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug")
514+
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
515+
@unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()")
516+
def test_main_thread_after_fork_from_nonmain_thread(self):
517+
code = """if 1:
518+
import os, threading, sys
519+
520+
def f():
521+
pid = os.fork()
522+
if pid == 0:
523+
main = threading.main_thread()
524+
print(main.name)
525+
print(main.ident == threading.current_thread().ident)
526+
print(main.ident == threading.get_ident())
527+
# stdout is fully buffered because not a tty,
528+
# we have to flush before exit.
529+
sys.stdout.flush()
530+
else:
531+
os.waitpid(pid, 0)
473532
474-
# Between fork() and exec(), only async-safe functions are allowed (issues
475-
# #12316 and #11870), and fork() from a worker thread is known to trigger
476-
# problems with some operating systems (issue #3863): skip problematic tests
477-
# on platforms known to behave badly.
478-
platforms_to_skip = ('freebsd4', 'freebsd5', 'freebsd6', 'netbsd5',
479-
'hp-ux11')
533+
th = threading.Thread(target=f)
534+
th.start()
535+
th.join()
536+
"""
537+
_, out, err = assert_python_ok("-c", code)
538+
data = out.decode().replace('\r', '')
539+
self.assertEqual(err, b"")
540+
self.assertEqual(data, "Thread-1\nTrue\nTrue\n")
541+
542+
543+
class ThreadJoinOnShutdown(BaseTestCase):
480544

481545
def _run_and_join(self, script):
482546
script = """if 1:

Lib/threading.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -840,20 +840,6 @@ def __init__(self):
840840
with _active_limbo_lock:
841841
_active[self._ident] = self
842842

843-
def _exitfunc(self):
844-
self._stop()
845-
t = _pickSomeNonDaemonThread()
846-
while t:
847-
t.join()
848-
t = _pickSomeNonDaemonThread()
849-
self._delete()
850-
851-
def _pickSomeNonDaemonThread():
852-
for t in enumerate():
853-
if not t.daemon and t.is_alive():
854-
return t
855-
return None
856-
857843

858844
# Dummy thread class to represent threads not started here.
859845
# These aren't garbage collected when they die, nor can they be waited for.
@@ -915,7 +901,24 @@ def enumerate():
915901
# and make it available for the interpreter
916902
# (Py_Main) as threading._shutdown.
917903

918-
_shutdown = _MainThread()._exitfunc
904+
_main_thread = _MainThread()
905+
906+
def _shutdown():
907+
_main_thread._stop()
908+
t = _pickSomeNonDaemonThread()
909+
while t:
910+
t.join()
911+
t = _pickSomeNonDaemonThread()
912+
_main_thread._delete()
913+
914+
def _pickSomeNonDaemonThread():
915+
for t in enumerate():
916+
if not t.daemon and t.is_alive():
917+
return t
918+
return None
919+
920+
def main_thread():
921+
return _main_thread
919922

920923
# get thread-local implementation, either from the thread
921924
# module, or from the python fallback
@@ -933,12 +936,13 @@ def _after_fork():
933936

934937
# Reset _active_limbo_lock, in case we forked while the lock was held
935938
# by another (non-forked) thread. http://bugs.python.org/issue874900
936-
global _active_limbo_lock
939+
global _active_limbo_lock, _main_thread
937940
_active_limbo_lock = _allocate_lock()
938941

939942
# fork() only copied the current thread; clear references to others.
940943
new_active = {}
941944
current = current_thread()
945+
_main_thread = current
942946
with _active_limbo_lock:
943947
for thread in _enumerate():
944948
# Any lock/condition variable may be currently locked or in an

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Core and Builtins
5454
Library
5555
-------
5656

57+
- Issue #18882: Add threading.main_thread() function.
58+
5759
- Issue #18901: The sunau getparams method now returns a namedtuple rather than
5860
a plain tuple. Patch by Claudiu Popa.
5961

0 commit comments

Comments
 (0)