Merge branch 'threadpool-shared-thread-prios' into 'main'

GThreadPool: Always use the thread-spawning thread for the global shared thread pool

Closes #2769

See merge request GNOME/glib!3208
This commit is contained in:
Michael Catanzaro 2023-01-20 19:00:49 +00:00
commit ce876ab28b
7 changed files with 40 additions and 211 deletions

View File

@ -376,7 +376,7 @@ g_thread_create_full (GThreadFunc func,
GThread *thread; GThread *thread;
thread = g_thread_new_internal (NULL, g_deprecated_thread_proxy, thread = g_thread_new_internal (NULL, g_deprecated_thread_proxy,
func, data, stack_size, NULL, error); func, data, stack_size, error);
if (thread && !joinable) if (thread && !joinable)
{ {

View File

@ -1157,9 +1157,6 @@ typedef struct
GMutex lock; GMutex lock;
void *(*proxy) (void *); void *(*proxy) (void *);
/* Must be statically allocated and valid forever */
const GThreadSchedulerSettings *scheduler_settings;
} GThreadPosix; } GThreadPosix;
void void
@ -1175,103 +1172,9 @@ g_system_thread_free (GRealThread *thread)
g_slice_free (GThreadPosix, pt); g_slice_free (GThreadPosix, pt);
} }
gboolean
g_system_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings)
{
/* FIXME: Implement the same for macOS and the BSDs so it doesn't go through
* the fallback code using an additional thread. */
#if defined(HAVE_SYS_SCHED_GETATTR)
pid_t tid;
int res;
/* FIXME: The struct definition does not seem to be possible to pull in
* via any of the normal system headers and it's only declared in the
* kernel headers. That's why we hardcode 56 here right now. */
guint size = 56; /* Size as of Linux 5.3.9 */
guint flags = 0;
tid = (pid_t) syscall (SYS_gettid);
scheduler_settings->attr = g_malloc0 (size);
do
{
int errsv;
res = syscall (SYS_sched_getattr, tid, scheduler_settings->attr, size, flags);
errsv = errno;
if (res == -1)
{
if (errsv == EAGAIN)
{
continue;
}
else if (errsv == E2BIG)
{
g_assert (size < G_MAXINT);
size *= 2;
scheduler_settings->attr = g_realloc (scheduler_settings->attr, size);
/* Needs to be zero-initialized */
memset (scheduler_settings->attr, 0, size);
}
else
{
g_debug ("Failed to get thread scheduler attributes: %s", g_strerror (errsv));
g_free (scheduler_settings->attr);
return FALSE;
}
}
}
while (res == -1);
/* Try setting them on the current thread to see if any system policies are
* in place that would disallow doing so */
res = syscall (SYS_sched_setattr, tid, scheduler_settings->attr, flags);
if (res == -1)
{
int errsv = errno;
g_debug ("Failed to set thread scheduler attributes: %s", g_strerror (errsv));
g_free (scheduler_settings->attr);
return FALSE;
}
return TRUE;
#else
return FALSE;
#endif
}
#if defined(HAVE_SYS_SCHED_GETATTR)
static void *
linux_pthread_proxy (void *data)
{
GThreadPosix *thread = data;
/* Set scheduler settings first if requested */
if (thread->scheduler_settings)
{
pid_t tid = 0;
guint flags = 0;
int res;
int errsv;
tid = (pid_t) syscall (SYS_gettid);
res = syscall (SYS_sched_setattr, tid, thread->scheduler_settings->attr, flags);
errsv = errno;
if (res == -1)
g_error ("Failed to set scheduler settings: %s", g_strerror (errsv));
}
return thread->proxy (data);
}
#endif
GRealThread * GRealThread *
g_system_thread_new (GThreadFunc proxy, g_system_thread_new (GThreadFunc proxy,
gulong stack_size, gulong stack_size,
const GThreadSchedulerSettings *scheduler_settings,
const char *name, const char *name,
GThreadFunc func, GThreadFunc func,
gpointer data, gpointer data,
@ -1290,7 +1193,6 @@ g_system_thread_new (GThreadFunc proxy,
base_thread->thread.func = func; base_thread->thread.func = func;
base_thread->thread.data = data; base_thread->thread.data = data;
base_thread->name = g_strdup (name); base_thread->name = g_strdup (name);
thread->scheduler_settings = scheduler_settings;
thread->proxy = proxy; thread->proxy = proxy;
posix_check_cmd (pthread_attr_init (&attr)); posix_check_cmd (pthread_attr_init (&attr));
@ -1310,18 +1212,13 @@ g_system_thread_new (GThreadFunc proxy,
#endif /* HAVE_PTHREAD_ATTR_SETSTACKSIZE */ #endif /* HAVE_PTHREAD_ATTR_SETSTACKSIZE */
#ifdef HAVE_PTHREAD_ATTR_SETINHERITSCHED #ifdef HAVE_PTHREAD_ATTR_SETINHERITSCHED
if (!scheduler_settings)
{ {
/* While this is the default, better be explicit about it */ /* While this is the default, better be explicit about it */
pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED); pthread_attr_setinheritsched (&attr, PTHREAD_INHERIT_SCHED);
} }
#endif /* HAVE_PTHREAD_ATTR_SETINHERITSCHED */ #endif /* HAVE_PTHREAD_ATTR_SETINHERITSCHED */
#if defined(HAVE_SYS_SCHED_GETATTR)
ret = pthread_create (&thread->system_thread, &attr, linux_pthread_proxy, thread);
#else
ret = pthread_create (&thread->system_thread, &attr, (void* (*)(void*))proxy, thread); ret = pthread_create (&thread->system_thread, &attr, (void* (*)(void*))proxy, thread);
#endif
posix_check_cmd (pthread_attr_destroy (&attr)); posix_check_cmd (pthread_attr_destroy (&attr));

View File

@ -463,19 +463,9 @@ g_thread_win32_proxy (gpointer data)
return 0; return 0;
} }
gboolean
g_system_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings)
{
HANDLE current_thread = GetCurrentThread ();
scheduler_settings->thread_prio = GetThreadPriority (current_thread);
return TRUE;
}
GRealThread * GRealThread *
g_system_thread_new (GThreadFunc proxy, g_system_thread_new (GThreadFunc proxy,
gulong stack_size, gulong stack_size,
const GThreadSchedulerSettings *scheduler_settings,
const char *name, const char *name,
GThreadFunc func, GThreadFunc func,
gpointer data, gpointer data,
@ -515,16 +505,10 @@ g_system_thread_new (GThreadFunc proxy,
* On Windows, by default all new threads are created with NORMAL thread * On Windows, by default all new threads are created with NORMAL thread
* priority. * priority.
*/ */
{
if (scheduler_settings) HANDLE current_thread = GetCurrentThread ();
{ thread_prio = GetThreadPriority (current_thread);
thread_prio = scheduler_settings->thread_prio; }
}
else
{
HANDLE current_thread = GetCurrentThread ();
thread_prio = GetThreadPriority (current_thread);
}
if (thread_prio == THREAD_PRIORITY_ERROR_RETURN) if (thread_prio == THREAD_PRIORITY_ERROR_RETURN)
{ {

View File

@ -885,7 +885,7 @@ g_thread_new (const gchar *name,
GError *error = NULL; GError *error = NULL;
GThread *thread; GThread *thread;
thread = g_thread_new_internal (name, g_thread_proxy, func, data, 0, NULL, &error); thread = g_thread_new_internal (name, g_thread_proxy, func, data, 0, &error);
if G_UNLIKELY (thread == NULL) if G_UNLIKELY (thread == NULL)
g_error ("creating thread '%s': %s", name ? name : "", error->message); g_error ("creating thread '%s': %s", name ? name : "", error->message);
@ -916,7 +916,7 @@ g_thread_try_new (const gchar *name,
gpointer data, gpointer data,
GError **error) GError **error)
{ {
return g_thread_new_internal (name, g_thread_proxy, func, data, 0, NULL, error); return g_thread_new_internal (name, g_thread_proxy, func, data, 0, error);
} }
GThread * GThread *
@ -925,7 +925,6 @@ g_thread_new_internal (const gchar *name,
GThreadFunc func, GThreadFunc func,
gpointer data, gpointer data,
gsize stack_size, gsize stack_size,
const GThreadSchedulerSettings *scheduler_settings,
GError **error) GError **error)
{ {
g_return_val_if_fail (func != NULL, NULL); g_return_val_if_fail (func != NULL, NULL);
@ -933,16 +932,7 @@ g_thread_new_internal (const gchar *name,
g_atomic_int_inc (&g_thread_n_created_counter); g_atomic_int_inc (&g_thread_n_created_counter);
g_trace_mark (G_TRACE_CURRENT_TIME, 0, "GLib", "GThread created", "%s", name ? name : "(unnamed)"); g_trace_mark (G_TRACE_CURRENT_TIME, 0, "GLib", "GThread created", "%s", name ? name : "(unnamed)");
return (GThread *) g_system_thread_new (proxy, stack_size, scheduler_settings, return (GThread *) g_system_thread_new (proxy, stack_size, name, func, data, error);
name, func, data, error);
}
gboolean
g_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings)
{
g_return_val_if_fail (scheduler_settings != NULL, FALSE);
return g_system_thread_get_scheduler_settings (scheduler_settings);
} }
/** /**

View File

@ -117,9 +117,6 @@ static gint max_unused_threads = 2;
static gint kill_unused_threads = 0; static gint kill_unused_threads = 0;
static guint max_idle_time = 15 * 1000; static guint max_idle_time = 15 * 1000;
static GThreadSchedulerSettings shared_thread_scheduler_settings;
static gboolean have_shared_thread_scheduler_settings = FALSE;
typedef struct typedef struct
{ {
/* Either thread or error are set in the end. Both transfer-full. */ /* Either thread or error are set in the end. Both transfer-full. */
@ -471,30 +468,22 @@ g_thread_pool_start_thread (GRealThreadPool *pool,
{ {
/* For non-exclusive thread-pools this can be called at any time /* For non-exclusive thread-pools this can be called at any time
* when a new thread is needed. We make sure to create a new thread * when a new thread is needed. We make sure to create a new thread
* here with the correct scheduler settings: either by directly * here with the correct scheduler settings by going via our helper
* providing them if supported by the GThread implementation or by * thread.
* going via our helper thread.
*/ */
if (have_shared_thread_scheduler_settings) SpawnThreadData spawn_thread_data = { (GThreadPool *) pool, NULL, NULL };
{
thread = g_thread_new_internal (name, g_thread_proxy, g_thread_pool_thread_proxy, pool, 0, &shared_thread_scheduler_settings, error);
}
else
{
SpawnThreadData spawn_thread_data = { (GThreadPool *) pool, NULL, NULL };
g_async_queue_lock (spawn_thread_queue); g_async_queue_lock (spawn_thread_queue);
g_async_queue_push_unlocked (spawn_thread_queue, &spawn_thread_data); g_async_queue_push_unlocked (spawn_thread_queue, &spawn_thread_data);
while (!spawn_thread_data.thread && !spawn_thread_data.error) while (!spawn_thread_data.thread && !spawn_thread_data.error)
g_cond_wait (&spawn_thread_cond, _g_async_queue_get_mutex (spawn_thread_queue)); g_cond_wait (&spawn_thread_cond, _g_async_queue_get_mutex (spawn_thread_queue));
thread = spawn_thread_data.thread; thread = spawn_thread_data.thread;
if (!thread) if (!thread)
g_propagate_error (error, g_steal_pointer (&spawn_thread_data.error)); g_propagate_error (error, g_steal_pointer (&spawn_thread_data.error));
g_async_queue_unlock (spawn_thread_queue); g_async_queue_unlock (spawn_thread_queue);
}
} }
if (thread == NULL) if (thread == NULL)
@ -547,6 +536,11 @@ g_thread_pool_start_thread (GRealThreadPool *pool,
* since their threads are never considered idle and returned to the * since their threads are never considered idle and returned to the
* global pool. * global pool.
* *
* Note that the threads used by exclusive threadpools will all inherit the
* scheduler settings of the current thread while the threads used by
* non-exclusive threadpools will inherit the scheduler settings from the
* first thread that created such a threadpool.
*
* @error can be %NULL to ignore errors, or non-%NULL to report * @error can be %NULL to ignore errors, or non-%NULL to report
* errors. An error can only occur when @exclusive is set to %TRUE * errors. An error can only occur when @exclusive is set to %TRUE
* and not all @max_threads threads could be created. * and not all @max_threads threads could be created.
@ -620,43 +614,31 @@ g_thread_pool_new_full (GFunc func,
if (!unused_thread_queue) if (!unused_thread_queue)
unused_thread_queue = g_async_queue_new (); unused_thread_queue = g_async_queue_new ();
/* For the very first non-exclusive thread-pool we remember the thread /*
* scheduler settings of the thread creating the pool, if supported by * Spawn a helper thread that is only responsible for spawning new threads
* the GThread implementation. This is then used for making sure that * with the scheduler settings of the current thread.
* all threads created on the non-exclusive thread-pool have the same *
* scheduler settings, and more importantly don't just inherit them * This is then used for making sure that all threads created on the
* from the thread that just happened to push a new task and caused * non-exclusive thread-pool have the same scheduler settings, and more
* a new thread to be created. * importantly don't just inherit them from the thread that just happened to
* push a new task and caused a new thread to be created.
* *
* Not doing so could cause real-time priority threads or otherwise * Not doing so could cause real-time priority threads or otherwise
* threads with problematic scheduler settings to be part of the * threads with problematic scheduler settings to be part of the
* non-exclusive thread-pools. * non-exclusive thread-pools.
* *
* If this is not supported by the GThread implementation then we here * For exclusive thread-pools this is not required as all threads are
* start a thread that will inherit the scheduler settings from this * created immediately below and are running forever, so they will
* very thread and whose only purpose is to spawn new threads with the
* same settings for use by the non-exclusive thread-pools.
*
*
* For non-exclusive thread-pools this is not required as all threads
* are created immediately below and are running forever, so they will
* automatically inherit the scheduler settings from this very thread. * automatically inherit the scheduler settings from this very thread.
*/ */
if (!exclusive && !have_shared_thread_scheduler_settings && !spawn_thread_queue) if (!exclusive && !spawn_thread_queue)
{ {
if (g_thread_get_scheduler_settings (&shared_thread_scheduler_settings)) GThread *pool_spawner = NULL;
{
have_shared_thread_scheduler_settings = TRUE;
}
else
{
GThread *pool_spawner = NULL;
spawn_thread_queue = g_async_queue_new (); spawn_thread_queue = g_async_queue_new ();
g_cond_init (&spawn_thread_cond); g_cond_init (&spawn_thread_cond);
pool_spawner = g_thread_new ("pool-spawner", g_thread_pool_spawn_thread, NULL); pool_spawner = g_thread_new ("pool-spawner", g_thread_pool_spawn_thread, NULL);
g_ignore_leak (pool_spawner); g_ignore_leak (pool_spawner);
}
} }
G_UNLOCK (init); G_UNLOCK (init);

View File

@ -90,25 +90,10 @@ struct _GRealThread
#endif #endif
/* Platform-specific scheduler settings for a thread */
typedef struct
{
#if defined(HAVE_SYS_SCHED_GETATTR)
/* This is for modern Linux */
struct sched_attr *attr;
#elif defined(G_OS_WIN32)
gint thread_prio;
#else
/* TODO: Add support for macOS and the BSDs */
void *dummy;
#endif
} GThreadSchedulerSettings;
void g_system_thread_wait (GRealThread *thread); void g_system_thread_wait (GRealThread *thread);
GRealThread *g_system_thread_new (GThreadFunc proxy, GRealThread *g_system_thread_new (GThreadFunc proxy,
gulong stack_size, gulong stack_size,
const GThreadSchedulerSettings *scheduler_settings,
const char *name, const char *name,
GThreadFunc func, GThreadFunc func,
gpointer data, gpointer data,
@ -118,19 +103,14 @@ void g_system_thread_free (GRealThread *thread);
void g_system_thread_exit (void); void g_system_thread_exit (void);
void g_system_thread_set_name (const gchar *name); void g_system_thread_set_name (const gchar *name);
gboolean g_system_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings);
/* gthread.c */ /* gthread.c */
GThread *g_thread_new_internal (const gchar *name, GThread *g_thread_new_internal (const gchar *name,
GThreadFunc proxy, GThreadFunc proxy,
GThreadFunc func, GThreadFunc func,
gpointer data, gpointer data,
gsize stack_size, gsize stack_size,
const GThreadSchedulerSettings *scheduler_settings,
GError **error); GError **error);
gboolean g_thread_get_scheduler_settings (GThreadSchedulerSettings *scheduler_settings);
gpointer g_thread_proxy (gpointer thread); gpointer g_thread_proxy (gpointer thread);
guint g_thread_n_created (void); guint g_thread_n_created (void);

View File

@ -1956,10 +1956,6 @@ else
glib_conf.set('HAVE_PTHREAD_GETNAME_NP', 1) glib_conf.set('HAVE_PTHREAD_GETNAME_NP', 1)
endif endif
if cc.has_header_symbol('sys/syscall.h', 'SYS_sched_getattr')
glib_conf.set('HAVE_SYS_SCHED_GETATTR', 1)
endif
# Assume that pthread_setname_np is available in some form; same as configure # Assume that pthread_setname_np is available in some form; same as configure
if cc.links(pthread_prefix + ''' if cc.links(pthread_prefix + '''
int main() { int main() {