bpo-36084: Add native thread ID to threading.Thread objects (GH-11993)

This commit is contained in:
Jake Tesler 2019-05-12 10:08:24 -07:00 committed by Antoine Pitrou
parent 87068ed009
commit 4959c33d25
10 changed files with 133 additions and 2 deletions

View file

@ -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

View file

@ -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,25 @@ 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 or ``0`` if the thread has not
been started. This is a non-negative integer. 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.
.. 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.

View file

@ -25,6 +25,7 @@ PyAPI_FUNC(void) PyThread_init_thread(void);
PyAPI_FUNC(unsigned long) PyThread_start_new_thread(void (*)(void *), void *); 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);
PyAPI_FUNC(unsigned long) PyThread_get_thread_native_id(void);
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);

View file

@ -71,6 +71,10 @@ def get_ident():
""" """
return 1 return 1
def get_native_id():
"""Dummy implementation of _thread.get_native_id()."""
return 0
def allocate_lock(): def allocate_lock():
"""Dummy implementation of _thread.allocate_lock().""" """Dummy implementation of _thread.allocate_lock()."""
return LockType() return LockType()

View file

@ -104,6 +104,10 @@ class ThreadTests(BaseTestCase):
self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$') self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$')
t.start() t.start()
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:

View file

@ -23,8 +23,8 @@ except ImportError:
# with the multiprocessing module, which doesn't provide the old # with the multiprocessing module, which doesn't provide the old
# Java inspired names. # Java inspired names.
__all__ = ['get_ident', 'active_count', 'Condition', 'current_thread', __all__ = ['get_ident', 'get_native_id', 'active_count', 'Condition',
'enumerate', 'main_thread', 'TIMEOUT_MAX', 'current_thread', 'enumerate', 'main_thread', 'TIMEOUT_MAX',
'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', 'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread',
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', 'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError',
'setprofile', 'settrace', 'local', 'stack_size'] 'setprofile', 'settrace', 'local', 'stack_size']
@ -34,6 +34,7 @@ _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
get_native_id = _thread.get_native_id
ThreadError = _thread.error ThreadError = _thread.error
try: try:
_CRLock = _thread.RLock _CRLock = _thread.RLock
@ -790,6 +791,7 @@ class Thread:
else: else:
self._daemonic = current_thread().daemon self._daemonic = current_thread().daemon
self._ident = None self._ident = None
self._native_id = 0
self._tstate_lock = None self._tstate_lock = None
self._started = Event() self._started = Event()
self._is_stopped = False self._is_stopped = False
@ -891,6 +893,9 @@ class Thread:
def _set_ident(self): def _set_ident(self):
self._ident = get_ident() self._ident = get_ident()
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 +908,7 @@ class Thread:
try: try:
self._set_ident() self._set_ident()
self._set_tstate_lock() self._set_tstate_lock()
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 +1083,17 @@ class Thread:
assert self._initialized, "Thread.__init__() not called" assert self._initialized, "Thread.__init__() not called"
return self._ident return self._ident
@property
def native_id(self):
"""Native integral thread ID of this thread or 0 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 +1193,7 @@ class _MainThread(Thread):
self._set_tstate_lock() self._set_tstate_lock()
self._started.set() self._started.set()
self._set_ident() self._set_ident()
self._set_native_id()
with _active_limbo_lock: with _active_limbo_lock:
_active[self._ident] = self _active[self._ident] = self
@ -1195,6 +1213,7 @@ class _DummyThread(Thread):
self._started.set() self._started.set()
self._set_ident() self._set_ident()
self._set_native_id()
with _active_limbo_lock: with _active_limbo_lock:
_active[self._ident] = self _active[self._ident] = self

View file

@ -0,0 +1 @@
Add native thread ID (TID) to threading.Thread objects

View file

@ -1159,6 +1159,20 @@ 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.");
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.");
static PyObject * static PyObject *
thread__count(PyObject *self, PyObject *Py_UNUSED(ignored)) thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
{ {
@ -1310,6 +1324,8 @@ 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},
{"get_native_id", thread_get_native_id,
METH_NOARGS, get_native_id_doc},
{"_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,

View file

@ -143,6 +143,8 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex)
unsigned long PyThread_get_thread_ident(void); unsigned long PyThread_get_thread_ident(void);
unsigned long PyThread_get_thread_native_id(void);
/* /*
* Initialization of the C package, should not be needed. * Initialization of the C package, should not be needed.
*/ */
@ -227,6 +229,20 @@ PyThread_get_thread_ident(void)
return GetCurrentThreadId(); return GetCurrentThreadId();
} }
/*
* 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();
return GetCurrentThreadId();
}
void _Py_NO_RETURN void _Py_NO_RETURN
PyThread_exit_thread(void) PyThread_exit_thread(void)
{ {

View file

@ -12,6 +12,12 @@
#endif #endif
#include <signal.h> #include <signal.h>
#if defined(__linux__)
#include <sys/syscall.h>
#elif defined(__FreeBSD__)
#include <pthread_np.h>
#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,27 @@ PyThread_get_thread_ident(void)
return (unsigned long) threadid; return (unsigned long) threadid;
} }
unsigned long
PyThread_get_thread_native_id(void)
{
if (!initialized)
PyThread_init_thread();
#ifdef __APPLE__
uint64_t native_id;
pthread_threadid_np(NULL, &native_id);
#elif defined(__linux__)
pid_t native_id;
native_id = syscall(__NR_gettid);
#elif defined(__FreeBSD__)
pid_t native_id;
native_id = pthread_getthreadid_np();
#else
unsigned long native_id;
native_id = 0;
#endif
return (unsigned long) native_id;
}
void _Py_NO_RETURN void _Py_NO_RETURN
PyThread_exit_thread(void) PyThread_exit_thread(void)
{ {