#include <glib.h>
#include <stdio.h>

typedef void (* GThreadPoolParallelFunc) (guint task, gpointer user_data);

typedef struct
{
  GThreadPoolParallelFunc func;
  gpointer                user_data;

  guint   next_task;              /* atomic */
  guint   n_tasks;                /* read-only */
  guint   num_tasks_pending;      /* protected by lock */
  GMutex lock;
  GCond  cond;
} ParallelData;

static void
g_thread_pool_run_parallel_worker (gpointer data_,
                                   gpointer user_data)
{
  ParallelData *data = data_;
  guint task;

  while ((task = g_atomic_int_add (&data->next_task, 1)) < data->n_tasks)
    (* data->func) (task, data->user_data);

  g_mutex_lock (&data->lock);
  data->num_tasks_pending--;
  if (data->num_tasks_pending == 0)
    g_cond_signal (&data->cond);
  g_mutex_unlock (&data->lock);
}

void
g_thread_pool_run_parallel (guint                    n_tasks,
                            GThreadPoolParallelFunc  func,
                            gpointer                 user_data)
{
  static GThreadPool *the_big_pool = NULL;
  guint threads = g_get_num_processors ();
  guint task;
  ParallelData data;

  data.func = func;
  data.user_data = user_data;
  data.next_task = 0;
  data.n_tasks = n_tasks;

  if (threads > n_tasks)
    threads = n_tasks;

  if (threads > 1)
    {
      gint i;

      data.num_tasks_pending = threads - 1;
      g_mutex_init (&data.lock);
      g_cond_init (&data.cond);

      if (!the_big_pool)
        the_big_pool = g_thread_pool_new (g_thread_pool_run_parallel_worker, NULL, -1, FALSE, NULL);

      for (i = 0; i < threads - 1; i++)
        g_thread_pool_push (the_big_pool, &data, NULL);
    }

  while ((task = g_atomic_int_add (&data.next_task, 1)) < n_tasks)
    (* func) (task, user_data);

  if (threads > 1)
    {
      g_mutex_lock (&data.lock);
      while (data.num_tasks_pending)
        g_cond_wait (&data.cond, &data.lock);
      g_mutex_unlock (&data.lock);

      g_mutex_clear (&data.lock);
      g_cond_clear (&data.cond);
    }
}

void
print_int (guint task, gpointer user_data)
{
  const gchar *str = user_data;

  printf ("%s: %u (thread %p)\n", str, task, g_thread_self ());
  g_usleep(G_TIME_SPAN_MILLISECOND);
}

int
main (void)
{
  g_thread_pool_run_parallel (500, print_int, (void *) "doing row");
  return 0;
}
