Skip to content

Commit b121f63

Browse files
jaketeslervstinner
authored andcommitted
bpo-36084: Add native thread ID (TID) to threading.Thread (GH-13463)
Add native thread ID (TID) to threading.Thread objects (supported platforms: Windows, FreeBSD, Linux, macOS).
1 parent b3be407 commit b121f63

File tree

9 files changed

+154
-0
lines changed

9 files changed

+154
-0
lines changed

Doc/library/_thread.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ This module defines the following constants and functions:
8585
may be recycled when a thread exits and another thread is created.
8686

8787

88+
.. function:: get_native_id()
89+
90+
Return the native integral Thread ID of the current thread assigned by the kernel.
91+
This is a non-negative integer.
92+
Its value may be used to uniquely identify this particular thread system-wide
93+
(until the thread terminates, after which the value may be recycled by the OS).
94+
95+
.. availability:: Windows, FreeBSD, Linux, macOS.
96+
97+
.. versionadded:: 3.8
98+
99+
88100
.. function:: stack_size([size])
89101

90102
Return the thread stack size used when creating new threads. The optional

Doc/library/threading.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ This module defines the following functions:
4949
.. versionadded:: 3.3
5050

5151

52+
.. function:: get_native_id()
53+
54+
Return the native integral Thread ID of the current thread assigned by the kernel.
55+
This is a non-negative integer.
56+
Its value may be used to uniquely identify this particular thread system-wide
57+
(until the thread terminates, after which the value may be recycled by the OS).
58+
59+
.. availability:: Windows, FreeBSD, Linux, macOS.
60+
61+
.. versionadded:: 3.8
62+
63+
5264
.. function:: enumerate()
5365

5466
Return a list of all :class:`Thread` objects currently alive. The list
@@ -297,6 +309,26 @@ since it is impossible to detect the termination of alien threads.
297309
another thread is created. The identifier is available even after the
298310
thread has exited.
299311

312+
.. attribute:: native_id
313+
314+
The native integral thread ID of this thread.
315+
This is a non-negative integer, or ``None`` if the thread has not
316+
been started. See the :func:`get_native_id` function.
317+
This represents the Thread ID (``TID``) as assigned to the
318+
thread by the OS (kernel). Its value may be used to uniquely identify
319+
this particular thread system-wide (until the thread terminates,
320+
after which the value may be recycled by the OS).
321+
322+
.. note::
323+
324+
Similar to Process IDs, Thread IDs are only valid (guaranteed unique
325+
system-wide) from the time the thread is created until the thread
326+
has been terminated.
327+
328+
.. availability:: Windows, FreeBSD, Linux, macOS.
329+
330+
.. versionadded:: 3.8
331+
300332
.. method:: is_alive()
301333

302334
Return whether the thread is alive.

Include/pythread.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *);
2626
PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void);
2727
PyAPI_FUNC(unsigned long) PyThread_get_thread_ident(void);
2828

29+
#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) || defined(_WIN32)
30+
#define PY_HAVE_THREAD_NATIVE_ID
31+
PyAPI_FUNC(unsigned long) PyThread_get_thread_native_id(void);
32+
#endif
33+
2934
PyAPI_FUNC(PyThread_type_lock) PyThread_allocate_lock(void);
3035
PyAPI_FUNC(void) PyThread_free_lock(PyThread_type_lock);
3136
PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int);

Lib/test/test_threading.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ def test_various_ops(self):
104104
self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$')
105105
t.start()
106106

107+
if hasattr(threading, 'get_native_id'):
108+
native_ids = set(t.native_id for t in threads) | {threading.get_native_id()}
109+
self.assertNotIn(None, native_ids)
110+
self.assertEqual(len(native_ids), NUMTASKS + 1)
111+
107112
if verbose:
108113
print('waiting for all tasks to complete')
109114
for t in threads:

Lib/threading.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@
3434
_allocate_lock = _thread.allocate_lock
3535
_set_sentinel = _thread._set_sentinel
3636
get_ident = _thread.get_ident
37+
try:
38+
get_native_id = _thread.get_native_id
39+
_HAVE_THREAD_NATIVE_ID = True
40+
__all__.append('get_native_id')
41+
except AttributeError:
42+
_HAVE_THREAD_NATIVE_ID = False
3743
ThreadError = _thread.error
3844
try:
3945
_CRLock = _thread.RLock
@@ -790,6 +796,8 @@ class is implemented.
790796
else:
791797
self._daemonic = current_thread().daemon
792798
self._ident = None
799+
if _HAVE_THREAD_NATIVE_ID:
800+
self._native_id = None
793801
self._tstate_lock = None
794802
self._started = Event()
795803
self._is_stopped = False
@@ -891,6 +899,10 @@ def _bootstrap(self):
891899
def _set_ident(self):
892900
self._ident = get_ident()
893901

902+
if _HAVE_THREAD_NATIVE_ID:
903+
def _set_native_id(self):
904+
self._native_id = get_native_id()
905+
894906
def _set_tstate_lock(self):
895907
"""
896908
Set a lock object which will be released by the interpreter when
@@ -903,6 +915,8 @@ def _bootstrap_inner(self):
903915
try:
904916
self._set_ident()
905917
self._set_tstate_lock()
918+
if _HAVE_THREAD_NATIVE_ID:
919+
self._set_native_id()
906920
self._started.set()
907921
with _active_limbo_lock:
908922
_active[self._ident] = self
@@ -1077,6 +1091,18 @@ def ident(self):
10771091
assert self._initialized, "Thread.__init__() not called"
10781092
return self._ident
10791093

1094+
if _HAVE_THREAD_NATIVE_ID:
1095+
@property
1096+
def native_id(self):
1097+
"""Native integral thread ID of this thread, or None if it has not been started.
1098+
1099+
This is a non-negative integer. See the get_native_id() function.
1100+
This represents the Thread ID as reported by the kernel.
1101+
1102+
"""
1103+
assert self._initialized, "Thread.__init__() not called"
1104+
return self._native_id
1105+
10801106
def is_alive(self):
10811107
"""Return whether the thread is alive.
10821108
@@ -1176,6 +1202,8 @@ def __init__(self):
11761202
self._set_tstate_lock()
11771203
self._started.set()
11781204
self._set_ident()
1205+
if _HAVE_THREAD_NATIVE_ID:
1206+
self._set_native_id()
11791207
with _active_limbo_lock:
11801208
_active[self._ident] = self
11811209

@@ -1195,6 +1223,8 @@ def __init__(self):
11951223

11961224
self._started.set()
11971225
self._set_ident()
1226+
if _HAVE_THREAD_NATIVE_ID:
1227+
self._set_native_id()
11981228
with _active_limbo_lock:
11991229
_active[self._ident] = self
12001230

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add native thread ID (TID) to threading.Thread objects (supported platforms: Windows, FreeBSD, Linux, macOS)

Modules/_threadmodule.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,22 @@ allocated consecutive numbers starting at 1, this behavior should not\n\
11591159
be relied upon, and the number should be seen purely as a magic cookie.\n\
11601160
A thread's identity may be reused for another thread after it exits.");
11611161

1162+
#ifdef PY_HAVE_THREAD_NATIVE_ID
1163+
static PyObject *
1164+
thread_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored))
1165+
{
1166+
unsigned long native_id = PyThread_get_thread_native_id();
1167+
return PyLong_FromUnsignedLong(native_id);
1168+
}
1169+
1170+
PyDoc_STRVAR(get_native_id_doc,
1171+
"get_native_id() -> integer\n\
1172+
\n\
1173+
Return a non-negative integer identifying the thread as reported\n\
1174+
by the OS (kernel). This may be used to uniquely identify a\n\
1175+
particular thread within a system.");
1176+
#endif
1177+
11621178
static PyObject *
11631179
thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
11641180
{
@@ -1310,6 +1326,10 @@ static PyMethodDef thread_methods[] = {
13101326
METH_NOARGS, interrupt_doc},
13111327
{"get_ident", thread_get_ident,
13121328
METH_NOARGS, get_ident_doc},
1329+
#ifdef PY_HAVE_THREAD_NATIVE_ID
1330+
{"get_native_id", thread_get_native_id,
1331+
METH_NOARGS, get_native_id_doc},
1332+
#endif
13131333
{"_count", thread__count,
13141334
METH_NOARGS, _count_doc},
13151335
{"stack_size", (PyCFunction)thread_stack_size,

Python/thread_nt.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex)
143143

144144
unsigned long PyThread_get_thread_ident(void);
145145

146+
#ifdef PY_HAVE_THREAD_NATIVE_ID
147+
unsigned long PyThread_get_thread_native_id(void);
148+
#endif
149+
146150
/*
147151
* Initialization of the C package, should not be needed.
148152
*/
@@ -227,6 +231,25 @@ PyThread_get_thread_ident(void)
227231
return GetCurrentThreadId();
228232
}
229233

234+
#ifdef PY_HAVE_THREAD_NATIVE_ID
235+
/*
236+
* Return the native Thread ID (TID) of the calling thread.
237+
* The native ID of a thread is valid and guaranteed to be unique system-wide
238+
* from the time the thread is created until the thread has been terminated.
239+
*/
240+
unsigned long
241+
PyThread_get_thread_native_id(void)
242+
{
243+
if (!initialized) {
244+
PyThread_init_thread();
245+
}
246+
247+
DWORD native_id;
248+
native_id = GetCurrentThreadId();
249+
return (unsigned long) native_id;
250+
}
251+
#endif
252+
230253
void _Py_NO_RETURN
231254
PyThread_exit_thread(void)
232255
{

Python/thread_pthread.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
#endif
1313
#include <signal.h>
1414

15+
#if defined(__linux__)
16+
# include <sys/syscall.h> /* syscall(SYS_gettid) */
17+
#elif defined(__FreeBSD__)
18+
# include <pthread_np.h> /* pthread_getthreadid_np() */
19+
#endif
20+
1521
/* The POSIX spec requires that use of pthread_attr_setstacksize
1622
be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */
1723
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
@@ -302,6 +308,26 @@ PyThread_get_thread_ident(void)
302308
return (unsigned long) threadid;
303309
}
304310

311+
#ifdef PY_HAVE_THREAD_NATIVE_ID
312+
unsigned long
313+
PyThread_get_thread_native_id(void)
314+
{
315+
if (!initialized)
316+
PyThread_init_thread();
317+
#ifdef __APPLE__
318+
uint64_t native_id;
319+
(void) pthread_threadid_np(NULL, &native_id);
320+
#elif defined(__linux__)
321+
pid_t native_id;
322+
native_id = syscall(SYS_gettid);
323+
#elif defined(__FreeBSD__)
324+
int native_id;
325+
native_id = pthread_getthreadid_np();
326+
#endif
327+
return (unsigned long) native_id;
328+
}
329+
#endif
330+
305331
void _Py_NO_RETURN
306332
PyThread_exit_thread(void)
307333
{

0 commit comments

Comments
 (0)