diff --git a/gdb/NEWS b/gdb/NEWS
index 03f46df5400..44e6ed799a4 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,13 @@
 
 *** Changes since GDB 17
 
+*   By default, GDB now remembers which thread was selected when you were
+  last working in each inferior.  When you switch back to that inferior,
+  GDB automatically restores the previous thread selection (if the thread
+  still exists).  You can disable this behavior with
+  'set remember-threads-per-inferior off' to use the original behavior
+  of always selecting the first available thread.
+
 * Support for .gdb_index sections with version less than 7 has been
   removed.
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index ceb69669ea6..dcbbcb273c1 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -3516,6 +3516,25 @@ To switch focus between inferiors, use the @code{inferior} command:
 Make inferior number @var{infno} the current inferior.  The argument
 @var{infno} is the inferior number assigned by @value{GDBN}, as shown
 in the first field of the @samp{info inferiors} display.
+
+By default, when switching to an inferior, @value{GDBN} will restore the
+thread that was previously selected in that inferior (if it still exists).
+This behavior can be controlled with the @code{set remember-threads-per-inferior}
+command, described below.
+@end table
+
+@cindex remember threads per inferior
+@kindex set remember-threads-per-inferior
+@kindex show remember-threads-per-inferior
+@table @code
+@item set remember-threads-per-inferior on|off
+Control whether @value{GDBN} remembers the selected thread for each inferior.
+When enabled (the default), switching to a previously used inferior will
+restore the thread that was active when you switched away.  When disabled,
+@value{GDBN} always selects the first available thread.
+
+@item show remember-threads-per-inferior
+Show whether @value{GDBN} remembers the selected thread for each inferior.
 @end table
 
 @vindex $_inferior@r{, convenience variable}
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 68d7aebbbf7..cecbef8be20 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -906,6 +906,10 @@ class scoped_restore_current_thread
   scoped_restore_current_language m_lang;
 };
 
+/* Return true if there is a current thread.  */
+
+bool has_current_thread ();
+
 /* Returns a pointer into the thread_info corresponding to
    INFERIOR_PTID.  INFERIOR_PTID *must* be in the thread list.  */
 extern struct thread_info* inferior_thread (void);
diff --git a/gdb/inferior.c b/gdb/inferior.c
index e050dec402e..5ea00839692 100644
--- a/gdb/inferior.c
+++ b/gdb/inferior.c
@@ -46,6 +46,9 @@ static int highest_inferior_num;
 /* See inferior.h.  */
 bool print_inferior_events = true;
 
+/* Control whether to remember selected threads per inferior.  */
+bool remember_inferior_threads = true;
+
 /* The Current Inferior.  This is a strong reference.  I.e., whenever
    an inferior is the current inferior, its refcount is
    incremented.  */
@@ -508,6 +511,25 @@ inferior_pid_to_str (int pid)
 
 /* See inferior.h.  */
 
+void
+save_inferior_last_thread ()
+{
+  if (has_current_thread ())
+    current_inferior ()->last_user_thread
+      = thread_info_ref::new_reference (inferior_thread ());
+  else
+    current_inferior ()->last_user_thread = nullptr;
+}
+
+static void
+inferiors_on_user_selected_context_changed (user_selected_what selection)
+{
+  if (remember_inferior_threads && (selection & USER_SELECTED_THREAD))
+    save_inferior_last_thread ();
+}
+
+/* See inferior.h.  */
+
 void
 print_selected_inferior (struct ui_out *uiout)
 {
@@ -771,8 +793,23 @@ inferior_command (const char *args, int from_tty)
 	{
 	  if (inf != current_inferior ())
 	    {
-	      thread_info *tp = any_thread_of_inferior (inf);
-	      if (tp == NULL)
+	      thread_info *tp = nullptr;
+
+	      if (remember_inferior_threads)
+		{
+		  thread_info *stored_tp = inf->last_user_thread.get ();
+
+		  /* Fallback to selecting any non-exited thread of
+		     inferior.  */
+		  tp = (stored_tp == nullptr
+			|| stored_tp->state == THREAD_EXITED)
+		    ? any_thread_of_inferior (inf)
+		    : stored_tp;
+		}
+	      else
+		tp = any_thread_of_inferior (inf);
+
+	      if (tp == nullptr)
 		error (_("Inferior has no threads."));
 
 	      switch_to_thread (tp);
@@ -1255,5 +1292,22 @@ Show printing of inferior events (such as inferior start and exit)."), NULL,
 	 show_print_inferior_events,
 	 &setprintlist, &showprintlist);
 
+  add_setshow_boolean_cmd ("remember-threads-per-inferior", no_class,
+	 &remember_inferior_threads, _("\
+Set whether GDB will cache the current thread in each inferior."), _("\
+Show whether GDB will cache the current thread in each inferior."), _("\
+When on, GDB records which thread was current when switching away from\n\
+an inferior, and automatically restores that thread when switching back\n\
+to that inferior (if the thread still exists).  When off, GDB selects\n\
+the first available thread."),
+	 NULL,
+	 NULL,
+	 &setlist, &showlist);
+
   create_internalvar_type_lazy ("_inferior", &inferior_funcs, NULL);
+
+  /* Observe user_selected_context_changed to store the current user
+     thread.  */
+  gdb::observers::user_selected_context_changed.attach (
+      inferiors_on_user_selected_context_changed, "inferiors");
 }
diff --git a/gdb/inferior.h b/gdb/inferior.h
index fbf9765fb0e..49f0406710c 100644
--- a/gdb/inferior.h
+++ b/gdb/inferior.h
@@ -265,6 +265,10 @@ extern int stopped_by_random_signal;
    `set print inferior-events'.  */
 extern bool print_inferior_events;
 
+/* Control whether GDB remembers the selected thread per inferior, set with
+   `set remember-threads-per-inferior'.  */
+extern bool remember_inferior_threads;
+
 /* Anything but NO_STOP_QUIETLY means we expect a trap and the caller
    will handle it themselves.  STOP_QUIETLY is used when running in
    the shell before the child program has been exec'd and when running
@@ -685,6 +689,10 @@ class inferior : public refcounted_object,
   /* Per inferior data-pointers required by other GDB modules.  */
   registry<inferior> registry_fields;
 
+  /* The last thread that was current when inferior was switched away
+     from.  */
+  thread_info_ref last_user_thread;
+
 private:
 
   /* Unpush TARGET and assert that it worked.  */
@@ -891,6 +899,12 @@ extern void print_selected_inferior (struct ui_out *uiout);
 extern void switch_to_inferior_and_push_target
   (inferior *new_inf, bool no_connection, inferior *org_inf);
 
+/* Record the selected thread of the current inferior when the user
+   switches a context.  GDB will select the recorded thread, if alive,
+   instead of any thread from the current inferior.  */
+
+extern void save_inferior_last_thread ();
+
 /* Return true if ID is a valid global inferior number.  */
 
 inline bool
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 9864b5bbdec..35292e13d95 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -4774,7 +4774,11 @@ fetch_inferior_event ()
 	    if (!non_stop
 		&& cmd_done
 		&& ecs.ws.kind () != TARGET_WAITKIND_NO_RESUMED)
-	      restore_thread.dont_restore ();
+	      {
+		restore_thread.dont_restore ();
+		if (remember_inferior_threads)
+		  save_inferior_last_thread ();
+	      }
 	  }
       }
 
diff --git a/gdb/testsuite/gdb.base/inferior-switch.c b/gdb/testsuite/gdb.base/inferior-switch.c
new file mode 100644
index 00000000000..107090d003c
--- /dev/null
+++ b/gdb/testsuite/gdb.base/inferior-switch.c
@@ -0,0 +1,42 @@
+/* This testcase is part of GDB, the GNU debugger.
+   Copyright 2024 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 <unistd.h>
+#include <pthread.h>
+
+static void *
+task (void *arg)
+{
+  volatile unsigned int duration = 0;
+
+  int a = 1; /* worker thread break 1.  */
+
+  sleep (duration);
+  int b = 2; /* worker thread break 2.  */
+
+  sleep (duration);
+  return NULL;
+}
+
+int
+main (void)
+{
+  pthread_t th;
+
+  alarm (30);
+
+  pthread_create (&th, NULL, task, NULL);
+  pthread_join (th, NULL);
+
+  return 0; /* main thread break.  */
+}
diff --git a/gdb/testsuite/gdb.base/inferior-switch.exp b/gdb/testsuite/gdb.base/inferior-switch.exp
new file mode 100644
index 00000000000..4ac1aa707c2
--- /dev/null
+++ b/gdb/testsuite/gdb.base/inferior-switch.exp
@@ -0,0 +1,172 @@
+# Copyright 2024 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/>.
+#
+# This test covers retaining of current thread when switching between
+# inferiors.
+#
+# It starts two inferiors that have 2 threads each, a main thread and a
+# thread that will have strategically placed sleeps to control execution
+# behaviour.
+#
+# test_explicit: Switch threads and switch between inferiors during stops.
+# Check that GDB remembers the previously selected thread of an inferior when
+# we switch to that inferior.
+#
+# test_implicit: Check that GDB remembers the previously selected thread of
+# an inferior when we are implicitly switched to another thread due to an
+# event.
+
+standard_testfile
+
+# This testcase explicitly creates another inferior.
+if {[use_gdb_stub]} {
+    return
+}
+
+if {[build_executable "failed to prepare" $testfile $srcfile \
+    {debug pthreads}] == -1} {
+    return -1
+}
+
+proc switch_inferior { num msg } {
+    gdb_test "inferior ${num}" "\\\[Switching to inferior ${num}.*" "${msg}"
+}
+
+proc switch_inferior_expect_thread { inf tid msg } {
+    gdb_test "inferior ${inf}" "\\\[Switching to thread $tid.*" "${msg}"
+}
+
+proc switch_thread { tid msg } {
+    gdb_test "thread $tid" "\\\[Switching to thread $tid.*" "${msg}"
+}
+
+# Common test preparation: start two inferiors and let them run to BP
+# at "worker thread break 1" location.
+
+proc_with_prefix prepare_test { } {
+    global binfile testfile
+
+    clean_restart $testfile
+
+    gdb_breakpoint [gdb_get_line_number "worker thread break 1"]
+
+    # Add another inferior, switch to it, load the binary and run it to after
+    # additional threads have started.
+    gdb_test "add-inferior" "Added inferior 2.*" \
+	"add new inferior"
+    switch_inferior 2 "switch to the new inferior 2"
+
+    gdb_test "file $binfile" ".*" "load binary for inferior 2"
+    gdb_test_no_output "set remote exec-file $binfile" \
+	"set remote exec-file"
+
+
+    gdb_test "run" \
+	"Thread 2.2.*hit Breakpoint 1.2.*" \
+	"inferior 2 run to breakpoint break"
+
+    switch_inferior 1 "switch to inferior 1"
+
+    gdb_test "run" \
+	"Thread 1.2.*hit Breakpoint 1.1.*" \
+	"inferior 1 run to breakpoint break"
+}
+
+# Test scenario for verifying that during stops, explicit inferior and thread
+# switches store the correct last user thread.
+
+proc_with_prefix test_explicit {} {
+    prepare_test
+
+    switch_inferior_expect_thread 2 2.2 "switch to inferior 2 thread 2.2"
+    switch_inferior_expect_thread 1 1.2 "back to inferior 1 thread 1.2"
+
+    gdb_continue_to_end "inferior 1" continue 1
+
+    switch_inferior_expect_thread 2 2.2 "again back to inferior 2 thread 2.2"
+
+    gdb_continue_to_end "inferior 2" continue 1
+}
+
+# Test scenario for verifying that during stops, implicit inferior and thread
+# switches store the correct last user thread.
+
+proc_with_prefix test_implicit {} {
+    prepare_test
+
+    gdb_breakpoint [gdb_get_line_number "worker thread break 2"]
+
+    # Enable sleeps in inferior 1 worker thread to slow it down.  Consequently
+    # breakpoint in inferior 2 will be hit first.
+    gdb_test "print duration = 2" " = 2"
+
+    switch_thread 2.1 "switch inferior 2 to main thread"
+    switch_thread 1.1 "switch inferior 1 to main thread"
+
+    gdb_test_no_output "set schedule-multiple on"
+
+    gdb_test "continue" \
+	".*Thread 2.2.*Breakpoint 2.2.*" \
+	"continue next breakpoint expect T2.2"
+
+    # Enable sleeps in inferior 2 worker thread to slow it down.  Consequently
+    # next breakpoint in inferior 1 will be hit first.
+    gdb_test "print duration = 3" " = 3"
+
+    gdb_test "continue" \
+	".*Thread 1.2.*Breakpoint 2.1.*" \
+	"continue next breakpoint expect T1.2"
+
+    switch_inferior_expect_thread 2 2.2 "switch inferior 2 stopped at 2nd BP"
+    switch_inferior_expect_thread 1 1.2 "switch inferior 1 stopped at 2nd BP"
+}
+
+# Test scenario for verifying that the feature can be disabled.
+
+proc_with_prefix test_disabled_behavior {} {
+    prepare_test
+
+    # Disable the feature
+    gdb_test_no_output "set remember-threads-per-inferior off" \
+	"disable remember-threads-per-inferior feature"
+
+    # Switch to thread 2.2
+    switch_thread 2.2 "switch to thread 2.2"
+
+    # Switch to inferior 1 - with feature disabled, should NOT remember thread 1.2,
+    # should pick first available (1.1)
+    gdb_test "inferior 1" "\\\[Switching to thread 1.1.*" \
+	"with feature off, switches to first thread"
+
+    # Switch to thread 1.2
+    switch_thread 1.2 "switch to thread 1.2"
+
+    # Switch to inferior 2 - with feature disabled, should NOT remember thread 2.2,
+    # should pick first available (2.1)
+    gdb_test "inferior 2" "\\\[Switching to thread 2.1.*" \
+	"with feature off, selects first thread in inferior"
+
+    # Re-enable the feature and verify it works again
+    gdb_test_no_output "set remember-threads-per-inferior on" \
+	"re-enable remember-threads-per-inferior feature"
+
+    # Now switching back to inferior 1 should remember 1.2
+    switch_inferior_expect_thread 1 1.2 \
+	"feature on again, restores previously selected thread 1.2"
+}
+
+test_explicit
+test_implicit
+test_disabled_behavior
diff --git a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
index 844206f7511..3b6f174a1a1 100644
--- a/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
+++ b/gdb/testsuite/gdb.mi/user-selected-context-sync.exp
@@ -52,6 +52,14 @@ set main_break_line [gdb_get_line_number "main break line"]
 set thread_loop_line [gdb_get_line_number "thread loop line"]
 set thread_caller_line [gdb_get_line_number "thread caller line"]
 
+# Reset to thread 2.1.
+
+proc reset_to_known_thread { pfx } {
+    with_test_prefix "$pfx" {
+	reset_selection "2.1"
+    }
+}
+
 # Return whether we expect thread THREAD to be running in mode MODE.
 #
 # MODE can be either "all-stop" or "non-stop".
@@ -1398,6 +1406,9 @@ proc do_test { mode } {
     }
     pass $test
 
+    # Reset to known state.
+    reset_to_known_thread "before testing CLI"
+
     # Test selecting inferior, thread and frame from CLI
 
     test_cli_inferior $mode
@@ -1411,6 +1422,9 @@ proc do_test { mode } {
     test_mi_thread_select $mode
     test_mi_stack_select_frame $mode
 
+    # Reset to known state.
+    reset_to_known_thread "before testing MI"
+
     # Test some CLI commands sent through MI, both with a "direct" command,
     # such as "thread 1", and with -interpreter-exec, such as
     # '-interpreter-exec console "thread 1"'.
diff --git a/gdb/thread.c b/gdb/thread.c
index 96e3bb7b50f..72b44a95122 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -85,6 +85,14 @@ is_current_thread (const thread_info *thr)
   return thr == current_thread_;
 }
 
+/* See gdbthread.h.  */
+
+bool
+has_current_thread ()
+{
+  return current_thread_ != nullptr;
+}
+
 struct thread_info*
 inferior_thread (void)
 {
