diff --git a/src/backends/native/meta-thread.c b/src/backends/native/meta-thread.c
index 93a84a8a5..1ed30441d 100644
--- a/src/backends/native/meta-thread.c
+++ b/src/backends/native/meta-thread.c
@@ -83,8 +83,12 @@ typedef struct _MetaThreadPrivate
   GThread *main_thread;
 
   struct {
+    MetaDBusRealtimeKit1 *rtkit_proxy;
     GThread *thread;
+    pid_t thread_id;
     GMutex init_mutex;
+    int realtime_inhibit_count;
+    gboolean is_realtime;
   } kernel;
 } MetaThreadPrivate;
 
@@ -202,15 +206,15 @@ get_rtkit_property (MetaDBusRealtimeKit1  *rtkit_proxy,
 }
 
 static gboolean
-request_realtime_scheduling (MetaThread  *thread,
-                             GError     **error)
+ensure_realtime_kit_proxy (MetaThread  *thread,
+                           GError     **error)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
   g_autoptr (MetaDBusRealtimeKit1) rtkit_proxy = NULL;
   g_autoptr (GError) local_error = NULL;
-  int64_t rttime;
-  struct rlimit rl;
-  uint32_t priority;
+
+  if (priv->kernel.rtkit_proxy)
+    return TRUE;
 
   rtkit_proxy =
     meta_dbus_realtime_kit1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
@@ -228,12 +232,29 @@ request_realtime_scheduling (MetaThread  *thread,
       return FALSE;
     }
 
-  priority = meta_dbus_realtime_kit1_get_max_realtime_priority (rtkit_proxy);
+  priv->kernel.rtkit_proxy = g_steal_pointer (&rtkit_proxy);
+  return TRUE;
+}
+
+static gboolean
+request_realtime_scheduling (MetaThread  *thread,
+                             GError     **error)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) local_error = NULL;
+  int64_t rttime;
+  struct rlimit rl;
+  uint32_t priority;
+
+  if (!ensure_realtime_kit_proxy (thread, error))
+    return FALSE;
+
+  priority = meta_dbus_realtime_kit1_get_max_realtime_priority (priv->kernel.rtkit_proxy);
   if (priority == 0)
     {
       g_autoptr (GVariant) priority_variant = NULL;
 
-      priority_variant = get_rtkit_property (rtkit_proxy,
+      priority_variant = get_rtkit_property (priv->kernel.rtkit_proxy,
                                              "MaxRealtimePriority",
                                              error);
       if (!priority_variant)
@@ -245,12 +266,12 @@ request_realtime_scheduling (MetaThread  *thread,
   if (priority == 0)
     g_warning ("Maximum real time scheduling priority is 0");
 
-  rttime = meta_dbus_realtime_kit1_get_rttime_usec_max (rtkit_proxy);
+  rttime = meta_dbus_realtime_kit1_get_rttime_usec_max (priv->kernel.rtkit_proxy);
   if (rttime == 0)
     {
       g_autoptr (GVariant) rttime_variant = NULL;
 
-      rttime_variant = get_rtkit_property (rtkit_proxy,
+      rttime_variant = get_rtkit_property (priv->kernel.rtkit_proxy,
                                            "RTTimeUSecMax",
                                            error);
       if (!rttime_variant)
@@ -273,8 +294,8 @@ request_realtime_scheduling (MetaThread  *thread,
 
   meta_topic (META_DEBUG_BACKEND, "Setting '%s' thread real time priority to %d",
               priv->name, priority);
-  if (!meta_dbus_realtime_kit1_call_make_thread_realtime_sync (rtkit_proxy,
-                                                               gettid (),
+  if (!meta_dbus_realtime_kit1_call_make_thread_realtime_sync (priv->kernel.rtkit_proxy,
+                                                               priv->kernel.thread_id,
                                                                priority,
                                                                NULL,
                                                                &local_error))
@@ -287,6 +308,90 @@ request_realtime_scheduling (MetaThread  *thread,
   return TRUE;
 }
 
+static gboolean
+request_normal_scheduling (MetaThread  *thread,
+                           GError     **error)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) local_error = NULL;
+
+  if (!ensure_realtime_kit_proxy (thread, error))
+    return FALSE;
+
+  meta_topic (META_DEBUG_BACKEND, "Setting '%s' thread to normal priority", priv->name);
+  if (!meta_dbus_realtime_kit1_call_make_thread_high_priority_sync (priv->kernel.rtkit_proxy,
+                                                                    priv->kernel.thread_id,
+                                                                    0 /* "normal" nice value */,
+                                                                    NULL,
+                                                                    &local_error))
+    {
+      g_dbus_error_strip_remote_error (local_error);
+      g_propagate_error (error, g_steal_pointer (&local_error));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+should_use_realtime_scheduling_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  gboolean should_use_realtime_scheduling = FALSE;
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_USER:
+      break;
+    case META_THREAD_TYPE_KERNEL:
+      if (priv->wants_realtime && priv->kernel.realtime_inhibit_count == 0)
+        should_use_realtime_scheduling = TRUE;
+      break;
+    }
+
+  return should_use_realtime_scheduling;
+}
+
+static void
+sync_realtime_scheduling_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) error = NULL;
+  gboolean should_be_realtime;
+
+  should_be_realtime = should_use_realtime_scheduling_in_impl (thread);
+
+  if (should_be_realtime == priv->kernel.is_realtime)
+    return;
+
+  if (should_be_realtime)
+    {
+      if (!request_realtime_scheduling (thread, &error))
+        {
+          g_warning ("Failed to make thread '%s' realtime scheduled: %s",
+                     priv->name, error->message);
+        }
+      else
+        {
+          meta_topic (META_DEBUG_BACKEND, "Made thread '%s' real-time scheduled", priv->name);
+          priv->kernel.is_realtime = TRUE;
+        }
+    }
+  else
+    {
+      if (!request_normal_scheduling (thread, &error))
+        {
+          g_warning ("Failed to make thread '%s' normally scheduled: %s",
+                     priv->name, error->message);
+        }
+      else
+        {
+          meta_topic (META_DEBUG_BACKEND, "Made thread '%s' normally scheduled", priv->name);
+          priv->kernel.is_realtime = FALSE;
+        }
+    }
+}
+
 static gpointer
 thread_impl_func (gpointer user_data)
 {
@@ -309,20 +414,16 @@ thread_impl_func (gpointer user_data)
   meta_profiler_register_thread (profiler, thread_context, priv->name);
 #endif
 
-  if (priv->wants_realtime)
-    {
-      g_autoptr (GError) error = NULL;
+  priv->kernel.thread_id = gettid ();
+  priv->kernel.realtime_inhibit_count = 0;
+  priv->kernel.is_realtime = FALSE;
 
-      if (!request_realtime_scheduling (thread, &error))
-        {
-          g_warning ("Failed to make thread '%s' realtime scheduled: %s",
-                     priv->name, error->message);
-        }
-      else
-        {
-          g_message ("Made thread '%s' realtime scheduled", priv->name);
-          run_flags |= META_THREAD_IMPL_RUN_FLAG_REALTIME;
-        }
+  sync_realtime_scheduling_in_impl (thread);
+
+  if (priv->kernel.is_realtime)
+    {
+      g_message ("Made thread '%s' realtime scheduled", priv->name);
+      run_flags |= META_THREAD_IMPL_RUN_FLAG_REALTIME;
     }
 
   meta_thread_impl_run (impl, run_flags);
@@ -549,6 +650,10 @@ finalize_thread_kernel (MetaThread *thread)
   meta_thread_impl_terminate (priv->impl);
   g_thread_join (priv->kernel.thread);
   priv->kernel.thread = NULL;
+  priv->kernel.thread_id = 0;
+
+  g_clear_object (&priv->kernel.rtkit_proxy);
+
   g_mutex_clear (&priv->kernel.init_mutex);
 }
 
@@ -1161,3 +1266,39 @@ meta_thread_is_waiting_for_impl_task (MetaThread *thread)
 
   return priv->waiting_for_impl_task;
 }
+
+void
+meta_thread_inhibit_realtime_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_KERNEL:
+      priv->kernel.realtime_inhibit_count++;
+
+      if (priv->kernel.realtime_inhibit_count == 1)
+        sync_realtime_scheduling_in_impl (thread);
+      break;
+    case META_THREAD_TYPE_USER:
+      break;
+    }
+}
+
+void
+meta_thread_uninhibit_realtime_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_KERNEL:
+      priv->kernel.realtime_inhibit_count--;
+
+      if (priv->kernel.realtime_inhibit_count == 0)
+        sync_realtime_scheduling_in_impl (thread);
+      break;
+    case META_THREAD_TYPE_USER:
+      break;
+    }
+}
diff --git a/src/backends/native/meta-thread.h b/src/backends/native/meta-thread.h
index f6c5c94f5..4765719ec 100644
--- a/src/backends/native/meta-thread.h
+++ b/src/backends/native/meta-thread.h
@@ -98,6 +98,9 @@ gboolean meta_thread_is_in_impl_task (MetaThread *thread);
 
 gboolean meta_thread_is_waiting_for_impl_task (MetaThread *thread);
 
+void meta_thread_inhibit_realtime_in_impl (MetaThread *thread);
+void meta_thread_uninhibit_realtime_in_impl (MetaThread *thread);
+
 #define meta_assert_in_thread_impl(thread) \
   g_assert (meta_thread_is_in_impl_task (thread))
 #define meta_assert_not_in_thread_impl(thread) \