diff --git a/gdk/gdkparalleltask.c b/gdk/gdkparalleltask.c
new file mode 100644
index 0000000000..4b9c49e609
--- /dev/null
+++ b/gdk/gdkparalleltask.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright © 2024 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#include "config.h"
+
+#include "gdkparalleltaskprivate.h"
+
+typedef struct _TaskData TaskData;
+
+struct _TaskData
+{
+ GdkTaskFunc task_func;
+ gpointer task_data;
+ int n_running_tasks;
+};
+
+static void
+gdk_parallel_task_thread_func (gpointer data,
+ gpointer unused)
+{
+ TaskData *task = data;
+
+ task->task_func (task->task_data);
+
+ g_atomic_int_add (&task->n_running_tasks, -1);
+}
+
+/**
+ * gdk_parallel_task_run:
+ * @task_func: the function to spawn
+ * @task_data: data to pass to the function
+ *
+ * Spawns the given function in many threads.
+ * Once all functions have exited, this function returns.
+ **/
+void
+gdk_parallel_task_run (GdkTaskFunc task_func,
+ gpointer task_data)
+{
+ static GThreadPool *pool;
+ TaskData task = {
+ .task_func = task_func,
+ .task_data = task_data,
+ };
+ int i, n_tasks;
+
+ if (g_once_init_enter (&pool))
+ {
+ GThreadPool *the_pool = g_thread_pool_new (gdk_parallel_task_thread_func,
+ NULL,
+ MAX (2, g_get_num_processors ()) - 1,
+ FALSE,
+ NULL);
+ g_once_init_leave (&pool, the_pool);
+ }
+
+ n_tasks = g_get_num_processors ();
+ task.n_running_tasks = n_tasks;
+ /* Start with 1 because we run 1 task ourselves */
+ for (i = 1; i < n_tasks; i++)
+ {
+ g_thread_pool_push (pool, &task, NULL);
+ }
+
+ gdk_parallel_task_thread_func (&task, NULL);
+
+ while (g_atomic_int_get (&task.n_running_tasks) > 0)
+ g_thread_yield ();
+}
+
diff --git a/gdk/gdkparalleltaskprivate.h b/gdk/gdkparalleltaskprivate.h
new file mode 100644
index 0000000000..a20fb72f95
--- /dev/null
+++ b/gdk/gdkparalleltaskprivate.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright © 2024 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#pragma once
+
+#include
+
+G_BEGIN_DECLS
+
+typedef void (* GdkTaskFunc) (gpointer user_data);
+
+void gdk_parallel_task_run (GdkTaskFunc task_func,
+ gpointer task_data);
+
+G_END_DECLS
+
diff --git a/gdk/meson.build b/gdk/meson.build
index 131d0f3018..b75349f291 100644
--- a/gdk/meson.build
+++ b/gdk/meson.build
@@ -52,6 +52,7 @@ gdk_public_sources = files([
'gdkmonitor.c',
'gdkpaintable.c',
'gdkpango.c',
+ 'gdkparalleltask.c',
'gdkpipeiostream.c',
'gdkpopup.c',
'gdkpopuplayout.c',