Commit b121f631 authored by Jake Tesler's avatar Jake Tesler Committed by Victor Stinner

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).
parent b3be4072
...@@ -85,6 +85,18 @@ This module defines the following constants and functions: ...@@ -85,6 +85,18 @@ This module defines the following constants and functions:
may be recycled when a thread exits and another thread is created. may be recycled when a thread exits and another thread is created.
.. function:: get_native_id()
Return the native integral Thread ID of the current thread assigned by the kernel.
This is a non-negative integer.
Its value may be used to uniquely identify this particular thread system-wide
(until the thread terminates, after which the value may be recycled by the OS).
.. availability:: Windows, FreeBSD, Linux, macOS.
.. versionadded:: 3.8
.. function:: stack_size([size]) .. function:: stack_size([size])
Return the thread stack size used when creating new threads. The optional Return the thread stack size used when creating new threads. The optional
......
...@@ -49,6 +49,18 @@ This module defines the following functions: ...@@ -49,6 +49,18 @@ This module defines the following functions:
.. versionadded:: 3.3 .. versionadded:: 3.3
.. function:: get_native_id()
Return the native integral Thread ID of the current thread assigned by the kernel.
This is a non-negative integer.
Its value may be used to uniquely identify this particular thread system-wide
(until the thread terminates, after which the value may be recycled by the OS).
.. availability:: Windows, FreeBSD, Linux, macOS.
.. versionadded:: 3.8
.. function:: enumerate() .. function:: enumerate()
Return a list of all :class:`Thread` objects currently alive. The list 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. ...@@ -297,6 +309,26 @@ since it is impossible to detect the termination of alien threads.
another thread is created. The identifier is available even after the another thread is created. The identifier is available even after the
thread has exited. thread has exited.
.. attribute:: native_id
The native integral thread ID of this thread.
This is a non-negative integer, or ``None`` if the thread has not
been started. See the :func:`get_native_id` function.
This represents the Thread ID (``TID``) as assigned to the
thread by the OS (kernel). Its value may be used to uniquely identify
this particular thread system-wide (until the thread terminates,
after which the value may be recycled by the OS).
.. note::
Similar to Process IDs, Thread IDs are only valid (guaranteed unique
system-wide) from the time the thread is created until the thread
has been terminated.
.. availability:: Windows, FreeBSD, Linux, macOS.
.. versionadded:: 3.8
.. method:: is_alive() .. method:: is_alive()
Return whether the thread is alive. Return whether the thread is alive.
......
...@@ -26,6 +26,11 @@ PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *); ...@@ -26,6 +26,11 @@ PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *);
PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void); PyAPI_FUNC(void) _Py_NO_RETURN PyThread_exit_thread(void);
PyAPI_FUNC(unsigned long) PyThread_get_thread_ident(void); PyAPI_FUNC(unsigned long) PyThread_get_thread_ident(void);
#if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) || defined(_WIN32)
#define PY_HAVE_THREAD_NATIVE_ID
PyAPI_FUNC(unsigned long) PyThread_get_thread_native_id(void);
#endif
PyAPI_FUNC(PyThread_type_lock) PyThread_allocate_lock(void); PyAPI_FUNC(PyThread_type_lock) PyThread_allocate_lock(void);
PyAPI_FUNC(void) PyThread_free_lock(PyThread_type_lock); PyAPI_FUNC(void) PyThread_free_lock(PyThread_type_lock);
PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int); PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int);
......
...@@ -104,6 +104,11 @@ class ThreadTests(BaseTestCase): ...@@ -104,6 +104,11 @@ class ThreadTests(BaseTestCase):
self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$') self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$')
t.start() t.start()
if hasattr(threading, 'get_native_id'):
native_ids = set(t.native_id for t in threads) | {threading.get_native_id()}
self.assertNotIn(None, native_ids)
self.assertEqual(len(native_ids), NUMTASKS + 1)
if verbose: if verbose:
print('waiting for all tasks to complete') print('waiting for all tasks to complete')
for t in threads: for t in threads:
......
...@@ -34,6 +34,12 @@ _start_new_thread = _thread.start_new_thread ...@@ -34,6 +34,12 @@ _start_new_thread = _thread.start_new_thread
_allocate_lock = _thread.allocate_lock _allocate_lock = _thread.allocate_lock
_set_sentinel = _thread._set_sentinel _set_sentinel = _thread._set_sentinel
get_ident = _thread.get_ident get_ident = _thread.get_ident
try:
get_native_id = _thread.get_native_id
_HAVE_THREAD_NATIVE_ID = True
__all__.append('get_native_id')
except AttributeError:
_HAVE_THREAD_NATIVE_ID = False
ThreadError = _thread.error ThreadError = _thread.error
try: try:
_CRLock = _thread.RLock _CRLock = _thread.RLock
...@@ -790,6 +796,8 @@ class Thread: ...@@ -790,6 +796,8 @@ class Thread:
else: else:
self._daemonic = current_thread().daemon self._daemonic = current_thread().daemon
self._ident = None self._ident = None
if _HAVE_THREAD_NATIVE_ID:
self._native_id = None
self._tstate_lock = None self._tstate_lock = None
self._started = Event() self._started = Event()
self._is_stopped = False self._is_stopped = False
...@@ -891,6 +899,10 @@ class Thread: ...@@ -891,6 +899,10 @@ class Thread:
def _set_ident(self): def _set_ident(self):
self._ident = get_ident() self._ident = get_ident()
if _HAVE_THREAD_NATIVE_ID:
def _set_native_id(self):
self._native_id = get_native_id()
def _set_tstate_lock(self): def _set_tstate_lock(self):
""" """
Set a lock object which will be released by the interpreter when Set a lock object which will be released by the interpreter when
...@@ -903,6 +915,8 @@ class Thread: ...@@ -903,6 +915,8 @@ class Thread:
try: try:
self._set_ident() self._set_ident()
self._set_tstate_lock() self._set_tstate_lock()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
self._started.set() self._started.set()
with _active_limbo_lock: with _active_limbo_lock:
_active[self._ident] = self _active[self._ident] = self
...@@ -1077,6 +1091,18 @@ class Thread: ...@@ -1077,6 +1091,18 @@ class Thread:
assert self._initialized, "Thread.__init__() not called" assert self._initialized, "Thread.__init__() not called"
return self._ident return self._ident
if _HAVE_THREAD_NATIVE_ID:
@property
def native_id(self):
"""Native integral thread ID of this thread, or None if it has not been started.
This is a non-negative integer. See the get_native_id() function.
This represents the Thread ID as reported by the kernel.
"""
assert self._initialized, "Thread.__init__() not called"
return self._native_id
def is_alive(self): def is_alive(self):
"""Return whether the thread is alive. """Return whether the thread is alive.
...@@ -1176,6 +1202,8 @@ class _MainThread(Thread): ...@@ -1176,6 +1202,8 @@ class _MainThread(Thread):
self._set_tstate_lock() self._set_tstate_lock()
self._started.set() self._started.set()
self._set_ident() self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
with _active_limbo_lock: with _active_limbo_lock:
_active[self._ident] = self _active[self._ident] = self
...@@ -1195,6 +1223,8 @@ class _DummyThread(Thread): ...@@ -1195,6 +1223,8 @@ class _DummyThread(Thread):
self._started.set() self._started.set()
self._set_ident() self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
with _active_limbo_lock: with _active_limbo_lock:
_active[self._ident] = self _active[self._ident] = self
......
Add native thread ID (TID) to threading.Thread objects (supported platforms: Windows, FreeBSD, Linux, macOS)
\ No newline at end of file
...@@ -1159,6 +1159,22 @@ allocated consecutive numbers starting at 1, this behavior should not\n\ ...@@ -1159,6 +1159,22 @@ allocated consecutive numbers starting at 1, this behavior should not\n\
be relied upon, and the number should be seen purely as a magic cookie.\n\ be relied upon, and the number should be seen purely as a magic cookie.\n\
A thread's identity may be reused for another thread after it exits."); A thread's identity may be reused for another thread after it exits.");
#ifdef PY_HAVE_THREAD_NATIVE_ID
static PyObject *
thread_get_native_id(PyObject *self, PyObject *Py_UNUSED(ignored))
{
unsigned long native_id = PyThread_get_thread_native_id();
return PyLong_FromUnsignedLong(native_id);
}
PyDoc_STRVAR(get_native_id_doc,
"get_native_id() -> integer\n\
\n\
Return a non-negative integer identifying the thread as reported\n\
by the OS (kernel). This may be used to uniquely identify a\n\
particular thread within a system.");
#endif
static PyObject * static PyObject *
thread__count(PyObject *self, PyObject *Py_UNUSED(ignored)) thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
{ {
...@@ -1310,6 +1326,10 @@ static PyMethodDef thread_methods[] = { ...@@ -1310,6 +1326,10 @@ static PyMethodDef thread_methods[] = {
METH_NOARGS, interrupt_doc}, METH_NOARGS, interrupt_doc},
{"get_ident", thread_get_ident, {"get_ident", thread_get_ident,
METH_NOARGS, get_ident_doc}, METH_NOARGS, get_ident_doc},
#ifdef PY_HAVE_THREAD_NATIVE_ID
{"get_native_id", thread_get_native_id,
METH_NOARGS, get_native_id_doc},
#endif
{"_count", thread__count, {"_count", thread__count,
METH_NOARGS, _count_doc}, METH_NOARGS, _count_doc},
{"stack_size", (PyCFunction)thread_stack_size, {"stack_size", (PyCFunction)thread_stack_size,
......
...@@ -143,6 +143,10 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex) ...@@ -143,6 +143,10 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex)
unsigned long PyThread_get_thread_ident(void); unsigned long PyThread_get_thread_ident(void);
#ifdef PY_HAVE_THREAD_NATIVE_ID
unsigned long PyThread_get_thread_native_id(void);
#endif
/* /*
* Initialization of the C package, should not be needed. * Initialization of the C package, should not be needed.
*/ */
...@@ -227,6 +231,25 @@ PyThread_get_thread_ident(void) ...@@ -227,6 +231,25 @@ PyThread_get_thread_ident(void)
return GetCurrentThreadId(); return GetCurrentThreadId();
} }
#ifdef PY_HAVE_THREAD_NATIVE_ID
/*
* Return the native Thread ID (TID) of the calling thread.
* The native ID of a thread is valid and guaranteed to be unique system-wide
* from the time the thread is created until the thread has been terminated.
*/
unsigned long
PyThread_get_thread_native_id(void)
{
if (!initialized) {
PyThread_init_thread();
}
DWORD native_id;
native_id = GetCurrentThreadId();
return (unsigned long) native_id;
}
#endif
void _Py_NO_RETURN void _Py_NO_RETURN
PyThread_exit_thread(void) PyThread_exit_thread(void)
{ {
......
...@@ -12,6 +12,12 @@ ...@@ -12,6 +12,12 @@
#endif #endif
#include <signal.h> #include <signal.h>
#if defined(__linux__)
# include <sys/syscall.h> /* syscall(SYS_gettid) */
#elif defined(__FreeBSD__)
# include <pthread_np.h> /* pthread_getthreadid_np() */
#endif
/* The POSIX spec requires that use of pthread_attr_setstacksize /* The POSIX spec requires that use of pthread_attr_setstacksize
be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */ be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */
#ifdef _POSIX_THREAD_ATTR_STACKSIZE #ifdef _POSIX_THREAD_ATTR_STACKSIZE
...@@ -302,6 +308,26 @@ PyThread_get_thread_ident(void) ...@@ -302,6 +308,26 @@ PyThread_get_thread_ident(void)
return (unsigned long) threadid; return (unsigned long) threadid;
} }
#ifdef PY_HAVE_THREAD_NATIVE_ID
unsigned long
PyThread_get_thread_native_id(void)
{
if (!initialized)
PyThread_init_thread();
#ifdef __APPLE__
uint64_t native_id;
(void) pthread_threadid_np(NULL, &native_id);
#elif defined(__linux__)
pid_t native_id;
native_id = syscall(SYS_gettid);
#elif defined(__FreeBSD__)
int native_id;
native_id = pthread_getthreadid_np();
#endif
return (unsigned long) native_id;
}
#endif
void _Py_NO_RETURN void _Py_NO_RETURN
PyThread_exit_thread(void) PyThread_exit_thread(void)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment