[Python-checkins] bpo-36084: Add native thread ID (TID) to threading.Thread (GH-13463)

Victor Stinner webhook-mailer at python.org
Wed May 22 11:43:22 EDT 2019


https://github.com/python/cpython/commit/b121f63155d8e3c7c42ab6122e36eaf7f5e9f7f5
commit: b121f63155d8e3c7c42ab6122e36eaf7f5e9f7f5
branch: master
author: Jake Tesler <jake.tesler at gmail.com>
committer: Victor Stinner <vstinner at redhat.com>
date: 2019-05-22T17:43:16+02:00
summary:

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).

files:
A Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst
M Doc/library/_thread.rst
M Doc/library/threading.rst
M Include/pythread.h
M Lib/test/test_threading.py
M Lib/threading.py
M Modules/_threadmodule.c
M Python/thread_nt.h
M Python/thread_pthread.h

diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst
index acffabf24bad..d7814f218b50 100644
--- a/Doc/library/_thread.rst
+++ b/Doc/library/_thread.rst
@@ -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
diff --git a/Doc/library/threading.rst b/Doc/library/threading.rst
index 22342803e5e6..1df512f1d632 100644
--- a/Doc/library/threading.rst
+++ b/Doc/library/threading.rst
@@ -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
@@ -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.
diff --git a/Include/pythread.h b/Include/pythread.h
index bc1d92cd1ff1..40f12d257c14 100644
--- a/Include/pythread.h
+++ b/Include/pythread.h
@@ -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);
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 2ddc77b266b5..33a25f3b9d23 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -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:
diff --git a/Lib/threading.py b/Lib/threading.py
index 0ebbd6776ef4..77a2baec2acc 100644
--- a/Lib/threading.py
+++ b/Lib/threading.py
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
 
@@ -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
 
@@ -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
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst b/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst
new file mode 100644
index 000000000000..fb28a6fec77a
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst	
@@ -0,0 +1 @@
+Add native thread ID (TID) to threading.Thread objects (supported platforms: Windows, FreeBSD, Linux, macOS)
\ No newline at end of file
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 3c02d8dd5145..fee25abe283a 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -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))
 {
@@ -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,
diff --git a/Python/thread_nt.h b/Python/thread_nt.h
index 5e00c3511460..a5246dd0504d 100644
--- a/Python/thread_nt.h
+++ b/Python/thread_nt.h
@@ -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.
  */
@@ -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)
 {
diff --git a/Python/thread_pthread.h b/Python/thread_pthread.h
index 4c106d9959c1..f57a1e7bb78b 100644
--- a/Python/thread_pthread.h
+++ b/Python/thread_pthread.h
@@ -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
@@ -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)
 {



More information about the Python-checkins mailing list