From b121f63155d8e3c7c42ab6122e36eaf7f5e9f7f5 Mon Sep 17 00:00:00 2001 From: Jake Tesler Date: Wed, 22 May 2019 08:43:17 -0700 Subject: 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). --- Doc/library/_thread.rst | 12 ++++++++ Doc/library/threading.rst | 32 ++++++++++++++++++++++ Include/pythread.h | 5 ++++ Lib/test/test_threading.py | 5 ++++ Lib/threading.py | 30 ++++++++++++++++++++ .../2019-02-22-23-03-20.bpo-36084.86Eh4X.rst | 1 + Modules/_threadmodule.c | 20 ++++++++++++++ Python/thread_nt.h | 23 ++++++++++++++++ Python/thread_pthread.h | 26 ++++++++++++++++++ 9 files changed, 154 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-02-22-23-03-20.bpo-36084.86Eh4X.rst diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst index acffabf..d7814f2 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 2234280..1df512f 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 bc1d92c..40f12d2 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 2ddc77b..33a25f3 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -104,6 +104,11 @@ class ThreadTests(BaseTestCase): self.assertRegex(repr(t), r'^$') 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 0ebbd67..77a2bae 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -34,6 +34,12 @@ _start_new_thread = _thread.start_new_thread _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 Thread: 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 @@ class Thread: 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 @@ class Thread: 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 @@ class Thread: 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 @@ class _MainThread(Thread): 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 @@ class _DummyThread(Thread): 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 0000000..fb28a6f --- /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 3c02d8d..fee25ab 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 5e00c35..a5246dd 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 4c106d9..f57a1e7 100644 --- a/Python/thread_pthread.h +++ b/Python/thread_pthread.h @@ -12,6 +12,12 @@ #endif #include +#if defined(__linux__) +# include /* syscall(SYS_gettid) */ +#elif defined(__FreeBSD__) +# include /* 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) { -- cgit v0.12