Skip to content

Commit

Permalink
bpo-36084: Add native thread ID (TID) to threading.Thread (GH-13463)
Browse files Browse the repository at this point in the history
Add native thread ID (TID) to threading.Thread objects
(supported platforms: Windows, FreeBSD, Linux, macOS).
  • Loading branch information
jaketesler authored and vstinner committed May 22, 2019
1 parent b3be407 commit b121f63
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Doc/library/_thread.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ This module defines the following constants and functions:
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])

Return the thread stack size used when creating new threads. The optional
Expand Down
32 changes: 32 additions & 0 deletions Doc/library/threading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ This module defines the following functions:
.. 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()

Return a list of all :class:`Thread` objects currently alive. The list
Expand Down Expand Up @@ -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
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()

Return whether the thread is alive.
Expand Down
5 changes: 5 additions & 0 deletions Include/pythread.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(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(void) PyThread_free_lock(PyThread_type_lock);
PyAPI_FUNC(int) PyThread_acquire_lock(PyThread_type_lock, int);
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ def test_various_ops(self):
self.assertRegex(repr(t), r'^<TestThread\(.*, initial\)>$')
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:
print('waiting for all tasks to complete')
for t in threads:
Expand Down
30 changes: 30 additions & 0 deletions Lib/threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
_allocate_lock = _thread.allocate_lock
_set_sentinel = _thread._set_sentinel
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
try:
_CRLock = _thread.RLock
Expand Down Expand Up @@ -790,6 +796,8 @@ class is implemented.
else:
self._daemonic = current_thread().daemon
self._ident = None
if _HAVE_THREAD_NATIVE_ID:
self._native_id = None
self._tstate_lock = None
self._started = Event()
self._is_stopped = False
Expand Down Expand Up @@ -891,6 +899,10 @@ def _bootstrap(self):
def _set_ident(self):
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):
"""
Set a lock object which will be released by the interpreter when
Expand All @@ -903,6 +915,8 @@ def _bootstrap_inner(self):
try:
self._set_ident()
self._set_tstate_lock()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
self._started.set()
with _active_limbo_lock:
_active[self._ident] = self
Expand Down Expand Up @@ -1077,6 +1091,18 @@ def ident(self):
assert self._initialized, "Thread.__init__() not called"
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):
"""Return whether the thread is alive.
Expand Down Expand Up @@ -1176,6 +1202,8 @@ def __init__(self):
self._set_tstate_lock()
self._started.set()
self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
with _active_limbo_lock:
_active[self._ident] = self

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

self._started.set()
self._set_ident()
if _HAVE_THREAD_NATIVE_ID:
self._set_native_id()
with _active_limbo_lock:
_active[self._ident] = self

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add native thread ID (TID) to threading.Thread objects (supported platforms: Windows, FreeBSD, Linux, macOS)
20 changes: 20 additions & 0 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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\
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 *
thread__count(PyObject *self, PyObject *Py_UNUSED(ignored))
{
Expand Down Expand Up @@ -1310,6 +1326,10 @@ static PyMethodDef thread_methods[] = {
METH_NOARGS, interrupt_doc},
{"get_ident", thread_get_ident,
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,
METH_NOARGS, _count_doc},
{"stack_size", (PyCFunction)thread_stack_size,
Expand Down
23 changes: 23 additions & 0 deletions Python/thread_nt.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ LeaveNonRecursiveMutex(PNRMUTEX mutex)

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.
*/
Expand Down Expand Up @@ -227,6 +231,25 @@ PyThread_get_thread_ident(void)
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
PyThread_exit_thread(void)
{
Expand Down
26 changes: 26 additions & 0 deletions Python/thread_pthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
#endif
#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
be conditional on _POSIX_THREAD_ATTR_STACKSIZE being defined. */
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
Expand Down Expand Up @@ -302,6 +308,26 @@ PyThread_get_thread_ident(void)
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
PyThread_exit_thread(void)
{
Expand Down

0 comments on commit b121f63

Please sign in to comment.