diff --git a/gdb/NEWS b/gdb/NEWS
index 649a3a9824a..a98dd5cedbc 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -140,6 +140,8 @@ info main
 
 * Python API
 
+  ** gdb.ThreadExitedEvent added.  Emits a ThreadEvent.
+
   ** The gdb.unwinder.Unwinder.name attribute is now read-only.
 
   ** The name argument passed to gdb.unwinder.Unwinder.__init__ must
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 02cb9ad4bc0..10997a2ce2f 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -3829,6 +3829,15 @@ This has a single attribute:
 The new thread.
 @end defvar
 
+@item events.thread_exited
+This is emitted when @value{GDBN} notices a thread has exited.  The event
+is of type @code{gdb.ThreadExitedEvent} which extends @code{gdb.ThreadEvent}.
+This has a single attribute:
+
+@defvar ThreadExitedEvent.inferior_thread
+The exiting thread.
+@end defvar
+
 @item events.gdb_exiting
 This is emitted when @value{GDBN} exits.  This event is not emitted if
 @value{GDBN} exits as a result of an internal error, or after an
@@ -3893,6 +3902,7 @@ make Python breakpoints thread-specific, for example
 (@pxref{python_breakpoint_thread,,The Breakpoint.thread attribute}).
 @end defvar
 
+@anchor{inferior_thread_ptid}
 @defvar InferiorThread.ptid
 ID of the thread, as assigned by the operating system.  This attribute is a
 tuple containing three integers.  The first is the Process ID (PID); the second
diff --git a/gdb/python/py-all-events.def b/gdb/python/py-all-events.def
index 4d711f7fbd1..aa28f2c6f8b 100644
--- a/gdb/python/py-all-events.def
+++ b/gdb/python/py-all-events.def
@@ -32,6 +32,7 @@ GDB_PY_DEFINE_EVENT(clear_objfiles)
 GDB_PY_DEFINE_EVENT(new_inferior)
 GDB_PY_DEFINE_EVENT(inferior_deleted)
 GDB_PY_DEFINE_EVENT(new_thread)
+GDB_PY_DEFINE_EVENT(thread_exited)
 GDB_PY_DEFINE_EVENT(inferior_call)
 GDB_PY_DEFINE_EVENT(memory_changed)
 GDB_PY_DEFINE_EVENT(register_changed)
diff --git a/gdb/python/py-event-types.def b/gdb/python/py-event-types.def
index 2215c5ebf94..395d6c07447 100644
--- a/gdb/python/py-event-types.def
+++ b/gdb/python/py-event-types.def
@@ -51,6 +51,11 @@ GDB_PY_DEFINE_EVENT_TYPE (new_thread,
 			  "GDB new thread event object",
 			  thread_event_object_type);
 
+GDB_PY_DEFINE_EVENT_TYPE (thread_exited,
+			  "ThreadExitedEvent",
+			  "GDB thread exited event object",
+			  event_object_type);
+
 GDB_PY_DEFINE_EVENT_TYPE (new_inferior,
 			  "NewInferiorEvent",
 			  "GDB new inferior event object",
diff --git a/gdb/python/py-event.h b/gdb/python/py-event.h
index 0a7d31d02e9..01574aa20dc 100644
--- a/gdb/python/py-event.h
+++ b/gdb/python/py-event.h
@@ -61,6 +61,10 @@ extern int emit_memory_changed_event (CORE_ADDR addr, ssize_t len);
 extern int evpy_emit_event (PyObject *event,
 			    eventregistry_object *registry);
 
+/* Emits a thread exit event for THREAD containing the attributes
+   global_num, name, num and ptid */
+extern int emit_thread_exit_event (thread_info * thread);
+
 extern gdbpy_ref<> create_event_object (PyTypeObject *py_type);
 
 /* thread events can either be thread specific or process wide.  If gdb is
diff --git a/gdb/python/py-inferior.c b/gdb/python/py-inferior.c
index 8c4871dfa89..af8bd8855a3 100644
--- a/gdb/python/py-inferior.c
+++ b/gdb/python/py-inferior.c
@@ -371,6 +371,9 @@ delete_thread_object (struct thread_info *tp, int ignore)
   if (inf_obj == NULL)
     return;
 
+  if (emit_thread_exit_event (tp) < 0)
+    gdbpy_print_stack ();
+
   auto it = inf_obj->threads->find (tp);
   if (it != inf_obj->threads->end ())
     {
diff --git a/gdb/python/py-threadevent.c b/gdb/python/py-threadevent.c
index 05a833def25..7633f375996 100644
--- a/gdb/python/py-threadevent.c
+++ b/gdb/python/py-threadevent.c
@@ -53,3 +53,25 @@ create_thread_event_object (PyTypeObject *py_type, PyObject *thread)
 
   return thread_event_obj;
 }
+
+/* Emits a thread exit event for THREAD containing the attributes
+   global_num, name, num and ptid */
+
+int
+emit_thread_exit_event (thread_info * thread)
+{
+  if (evregpy_no_listeners_p (gdb_py_events.thread_exited))
+    return 0;
+
+  auto py_thr = thread_to_thread_object (thread);
+
+  if (py_thr == nullptr)
+    return -1;
+
+  auto inf_thr = create_thread_event_object (&thread_exited_event_object_type,
+				     py_thr.get ());
+  if (inf_thr == nullptr)
+    return -1;
+
+  return evpy_emit_event (inf_thr.get (), gdb_py_events.thread_exited);
+}
diff --git a/gdb/testsuite/gdb.python/py-thread-exited.c b/gdb/testsuite/gdb.python/py-thread-exited.c
new file mode 100644
index 00000000000..d4ff55b1b44
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-thread-exited.c
@@ -0,0 +1,37 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2010-2022 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see  <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <signal.h>
+pthread_t thread2_id;
+pthread_t thread3_id;
+
+void* do_thread (void* d)
+{
+  return NULL;
+}
+
+int main (void)
+{
+  pthread_create (&thread2_id, NULL, do_thread, NULL);
+  pthread_join (thread2_id, NULL);
+  pthread_create (&thread3_id, NULL, do_thread, NULL);
+  pthread_join (thread3_id, NULL);
+  return 12;
+}
diff --git a/gdb/testsuite/gdb.python/py-thread-exited.exp b/gdb/testsuite/gdb.python/py-thread-exited.exp
new file mode 100644
index 00000000000..ec552fc8b6e
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-thread-exited.exp
@@ -0,0 +1,49 @@
+# Copyright (C) 2022-2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+if { ![support_displaced_stepping] } {
+    unsupported "displaced stepping"
+    return -1
+}
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+    return -1
+}
+
+save_vars { GDBFLAGS } {
+    clean_restart $testfile
+}
+
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/py-thread-exited.py]
+gdb_test_no_output "source ${pyfile}" "load python file"
+
+gdb_test "test-events" "Event testers registered."
+
+if ![runto_main] {
+    return -1
+}
+
+gdb_breakpoint 37 "last of main"
+
+gdb_continue_to_breakpoint "continue to breakpoint"
+
+gdb_test "python print(threadOneExit)" \
+	 ".*event type: thread-exited. global num: 2.*"
+gdb_test "python print(threadTwoExit)" \
+	 ".*event type: thread-exited. global num: 3.*"
diff --git a/gdb/testsuite/gdb.python/py-thread-exited.py b/gdb/testsuite/gdb.python/py-thread-exited.py
new file mode 100644
index 00000000000..51dfeb05ae4
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-thread-exited.py
@@ -0,0 +1,31 @@
+import gdb
+
+threadOneExit = ""
+threadTwoExit = ""
+# we don't want to overwrite the 2nd thread's exit event, thus
+# store it here. we don't care about it though.
+mainThreadExit = ""
+
+def thread_exited_handler(event):
+    global threadOneExit, threadTwoExit, mainThreadExit
+    print("{}".format(event))
+    assert isinstance(event, gdb.ThreadExitedEvent)
+    if threadOneExit == "":
+        threadOneExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num)
+    else:
+        if threadTwoExit == "":
+            threadTwoExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num)
+        else:
+            mainThreadExit = "event type: thread-exited. global num: {}".format(event.inferior_thread.global_num)
+
+class test_events(gdb.Command):
+    """Test events."""
+
+    def __init__(self):
+        gdb.Command.__init__(self, "test-events", gdb.COMMAND_STACK)
+
+    def invoke(self, arg, from_tty):
+        gdb.events.thread_exited.connect(thread_exited_handler)
+        print("Event testers registered.")
+
+test_events()
