/** * iris_thread_new: * @exclusive: the thread is exclusive * * Createa a new #IrisThread instance that can be used to queue work items * to be processed on the thread. * * If @exclusive, then the thread will not yield to the scheduler and * therefore will not participate in scheduler thread balancing. * * Return value: the newly created #IrisThread instance */ IrisThread* iris_thread_new (gboolean exclusive) { IrisThread *thread; iris_debug (IRIS_DEBUG_THREAD); #if LINUX #else pthread_once (&my_thread_once, _pthread_init); #endif thread = g_slice_new0 (IrisThread); thread->exclusive = exclusive; thread->queue = g_async_queue_new (); thread->mutex = g_mutex_new (); thread->thread = g_thread_create_full ((GThreadFunc)iris_thread_worker, thread, 0, /* stack size */ FALSE, /* joinable */ FALSE, /* system thread */ G_THREAD_PRIORITY_NORMAL, NULL); thread->scheduler = NULL; return thread; }
static gpointer iris_thread_worker (IrisThread *thread) { IrisMessage *message; GTimeVal timeout = {0,0}; g_return_val_if_fail (thread != NULL, NULL); g_return_val_if_fail (thread->queue != NULL, NULL); #if LINUX my_thread = thread; #else pthread_setspecific (my_thread, thread); #endif iris_debug_init_thread (); iris_debug (IRIS_DEBUG_THREAD); next_message: if (thread->exclusive) { message = g_async_queue_pop (thread->queue); } else { /* If we do not get any schedulers to work for within our * timeout period, we can safely shutdown. */ g_get_current_time (&timeout); g_time_val_add (&timeout, G_USEC_PER_SEC * 5); message = g_async_queue_timed_pop (thread->queue, &timeout); if (!message) { /* Make sure that the manager removes us from the * free thread list. */ iris_scheduler_manager_destroy (thread); /* make sure nothing was added while we * removed ourselves */ message = g_async_queue_try_pop (thread->queue); } } if (!message) return NULL; switch (message->what) { case MSG_MANAGE: iris_thread_handle_manage (thread, iris_message_get_pointer (message, "queue"), iris_message_get_boolean (message, "exclusive"), iris_message_get_boolean (message, "leader")); break; case MSG_SHUTDOWN: iris_thread_handle_shutdown (thread); break; default: g_warn_if_reached (); break; } goto next_message; }
static void iris_thread_worker_transient (IrisThread *thread, IrisQueue *queue) { IrisThreadWork *thread_work = NULL; GTimeVal tv_timeout = {0,0}; iris_debug (IRIS_DEBUG_THREAD); /* The transient mode worker is responsible for helping finish off as * many of the work items as fast as possible. It is not responsible * for asking for more helpers, just processing work items. When done * processing work items, it will yield itself back to the scheduler * manager. */ do { g_get_current_time (&tv_timeout); g_time_val_add (&tv_timeout, POP_WAIT_TIMEOUT); if ((thread_work = iris_queue_timed_pop (queue, &tv_timeout)) != NULL) { if (!VERIFY_THREAD_WORK (thread_work)) continue; iris_thread_work_run (thread_work); iris_thread_work_free (thread_work); } } while (thread_work != NULL); /* Yield our thread back to the scheduler manager */ iris_scheduler_manager_yield (thread); }
/** * iris_thread_shutdown: * @thread: An #IrisThread * * Sends a message to the thread asking it to shutdown. */ void iris_thread_shutdown (IrisThread *thread) { IrisMessage *message; iris_debug (IRIS_DEBUG_THREAD); g_return_if_fail (thread != NULL); message = iris_message_new (MSG_SHUTDOWN); g_async_queue_push (thread->queue, message); }
/** * iris_thread_print_stat: * @thread: An #IrisThread * * Prints the stats of an #IrisThread to standard output for analysis. * See iris_thread_stat() for programmatically access the statistics. */ void iris_thread_print_stat (IrisThread *thread) { iris_debug (IRIS_DEBUG_THREAD); g_mutex_lock (thread->mutex); g_fprintf (stderr, " Thread 0x%016lx Active: %3s Queue Size: %d\n", (long)thread->thread, thread->active != NULL ? "yes" : "no", thread->active != NULL ? iris_queue_length (thread->active) : 0); g_mutex_unlock (thread->mutex); }
static void iris_thread_worker_transient (IrisThread *thread, IrisQueue *queue) { IrisThreadWork *thread_work = NULL; GTimeVal tv_timeout = {0,0}; gboolean remove_work; iris_debug (IRIS_DEBUG_THREAD); /* The transient mode worker is responsible for helping finish off as * many of the work items as fast as possible. It is not responsible * for asking for more helpers, just processing work items. When done * processing work items, it will yield itself back to the scheduler * manager. */ do { g_get_current_time (&tv_timeout); g_time_val_add (&tv_timeout, POP_WAIT_TIMEOUT); thread_work = iris_queue_timed_pop_or_close (queue, &tv_timeout); if (thread_work != NULL) { if (!g_atomic_int_compare_and_exchange(&thread_work->taken, FALSE, TRUE)) { remove_work = g_atomic_int_get (&thread_work->remove); if (!remove_work) continue; } else remove_work = g_atomic_int_get (&thread_work->remove); if (!remove_work) iris_thread_work_run (thread_work); iris_thread_work_free (thread_work); } } while (thread_work != NULL); /* Remove the thread from the scheduler (if it's not already removed us due * to being in finalization), and yield our thread back to the scheduler manager */ if (g_atomic_int_get (&thread->scheduler->in_finalize) == FALSE) iris_scheduler_remove_thread (thread->scheduler, thread); g_atomic_pointer_set (&thread->scheduler, NULL); iris_scheduler_manager_yield (thread); }
/** * iris_thread_manage: * @thread: An #IrisThread * @queue: A #GAsyncQueue * @leader: If the thread is responsible for asking for more threads * * Sends a message to the thread asking it to retreive work items from * the queue. * * If @leader is %TRUE, then the thread will periodically ask the scheduler * manager to ask for more threads. */ void iris_thread_manage (IrisThread *thread, IrisQueue *queue, gboolean leader) { IrisMessage *message; g_return_if_fail (thread != NULL); g_return_if_fail (queue != NULL); iris_debug (IRIS_DEBUG_THREAD); message = iris_message_new_full (MSG_MANAGE, "exclusive", G_TYPE_BOOLEAN, thread->exclusive, "queue", G_TYPE_POINTER, queue, "leader", G_TYPE_BOOLEAN, leader, NULL); g_async_queue_push (thread->queue, message); }
/** * iris_thread_print_stat: * @thread: An #IrisThread * * Prints the stats of an #IrisThread to standard output for analysis. */ void iris_thread_print_stat (IrisThread *thread) { iris_debug (IRIS_DEBUG_THREAD); g_mutex_lock (thread->mutex); g_fprintf (stderr, " Thread 0x%016lx Sched 0x%016lx %s Work q. 0x%016lx\n" "\t Active: %3s Queue Size: %d\n", (long)thread->thread, (long)thread->scheduler, thread->scheduler == iris_get_default_control_scheduler()? "(ctrl) ": iris_get_default_work_scheduler()? "(work) ": " ", (long)thread->active, thread->active != NULL ? "yes" : "no", thread->active != NULL ? iris_queue_get_length (thread->active) : 0); g_mutex_unlock (thread->mutex); }
static void iris_thread_worker_exclusive (IrisThread *thread, IrisQueue *queue, gboolean leader) { GTimeVal tv_now = {0,0}; GTimeVal tv_req = {0,0}; IrisThreadWork *thread_work = NULL; gint per_quanta = 0; /* Completed items within the * last quanta. */ guint queued = 0; /* Items left in the queue at */ gboolean has_resized = FALSE; iris_debug (IRIS_DEBUG_THREAD); g_get_current_time (&tv_now); g_get_current_time (&tv_req); queued = iris_queue_length (queue); /* Since our thread is in exclusive mode, we are responsible for * asking the scheduler manager to add or remove threads based * on the demand of our work queue. * * If the scheduler has maxed out the number of threads it is * allowed, then we will not ask the scheduler to add more * threads and rebalance. */ get_next_item: if (G_LIKELY ((thread_work = iris_queue_pop (queue)) != NULL)) { if (!VERIFY_THREAD_WORK (thread_work)) goto get_next_item; iris_thread_work_run (thread_work); iris_thread_work_free (thread_work); per_quanta++; } else { #if 0 g_warning ("Exclusive thread is done managing, received NULL"); #endif return; } if (G_UNLIKELY (!thread->scheduler->maxed && leader)) { g_get_current_time (&tv_now); if (G_UNLIKELY (timeout_elapsed (&tv_now, &tv_req))) { /* We check to see if we have a bunch more work to do * or a potential edge case where we are processing about * the same speed as the pusher, but it creates enough * contention where we dont speed up. This is because * some schedulers will round-robin or steal. And unless * we look to add another thread even though we have nothing * in the queue, we know there are more coming. */ queued = iris_queue_length (queue); if (queued == 0 && !has_resized) { queued = per_quanta * 2; has_resized = TRUE; } if (per_quanta < queued) { /* make sure we are not maxed before asking */ if (!g_atomic_int_get (&thread->scheduler->maxed)) iris_scheduler_manager_request (thread->scheduler, per_quanta, queued); } per_quanta = 0; tv_req = tv_now; g_time_val_add (&tv_req, QUANTUM_USECS); } } goto get_next_item; }
static void iris_thread_worker_exclusive (IrisThread *thread, IrisQueue *queue, gboolean leader) { GTimeVal tv_now = {0,0}; GTimeVal tv_req = {0,0}; IrisThreadWork *thread_work = NULL; gint per_quanta = 0; /* Completed items within the * last quanta. */ guint queued = 0; /* Items left in the queue at */ gboolean has_resized = FALSE; gboolean remove_work; iris_debug (IRIS_DEBUG_THREAD); g_get_current_time (&tv_now); g_get_current_time (&tv_req); queued = iris_queue_get_length (queue); /* Since our thread is in exclusive mode, we are responsible for * asking the scheduler manager to add or remove threads based * on the demand of our work queue. * * If the scheduler has maxed out the number of threads it is * allowed, then we will not ask the scheduler to add more * threads and rebalance. */ get_next_item: if (G_LIKELY ((thread_work = iris_queue_pop (queue)) != NULL)) { if (!g_atomic_int_compare_and_exchange(&thread_work->taken, FALSE, TRUE)) { remove_work = g_atomic_int_get (&thread_work->remove); if (!remove_work) /* We lost a race with another thread (remember a lockfree * queue may pop the same item twice). */ goto get_next_item; /* else: We lost a race with iris_scheduler_unqueue() */ } else /* We won the race. 'remove' is honoured anyway if we can. */ remove_work = g_atomic_int_get (&thread_work->remove); if (!remove_work) { iris_thread_work_run (thread_work); per_quanta++; } iris_thread_work_free (thread_work); } else { /* Queue is closed, so scheduler is finalizing. The scheduler will be * waiting until we set thread->scheduler to NULL. */ g_atomic_pointer_set (&thread->scheduler, NULL); iris_scheduler_manager_yield (thread); return; } if (remove_work) goto get_next_item; if (G_UNLIKELY (!thread->scheduler->maxed && leader)) { g_get_current_time (&tv_now); if (G_UNLIKELY (timeout_elapsed (&tv_now, &tv_req))) { /* We check to see if we have a bunch more work to do * or a potential edge case where we are processing about * the same speed as the pusher, but it creates enough * contention where we dont speed up. This is because * some schedulers will round-robin or steal. And unless * we look to add another thread even though we have nothing * in the queue, we know there are more coming. */ queued = iris_queue_get_length (queue); if (queued == 0 && !has_resized) { queued = per_quanta * 2; has_resized = TRUE; } if (per_quanta < queued) { /* make sure we are not maxed before asking */ if (!g_atomic_int_get (&thread->scheduler->maxed)) iris_scheduler_manager_request (thread->scheduler, per_quanta, queued); } per_quanta = 0; tv_req = tv_now; g_time_val_add (&tv_req, QUANTUM_USECS); } } goto get_next_item; }