简体   繁体   中英

How to make a pthreads pool run arbitrary routines

I am implementing a threads pool using pthreads. All threads share a work queue and the host main thread pushes the work into this shared queue. My implementation will be used as a library, it should be able to run any applications, therefore I want the work here be an arbitrary routine with arbitrary number of arguments. I searched this on the Internet but didn't find any solution.

Part of most implementations is like this:

typedef struct
{
  void* (*routine)(void*); // work routine
  void* arg; //work routine argument
} _work;

typedef struct
{
  _work *work;
  pthrea_mutex_t work_lock;
  pthread_cond_t work_ready;
} _tpool;

static _tpool *tpool = NULL;

void* thread_routine(void* args)
{
  _work *work;
  while(1){
    pthread_mutex_lock(&tpool->work_lock);
    while(!tpool->work){
        pthread_cond_wait(&tpool->work_ready, &tpool->work_lock);
    }
    work = tpool->work;
    pthread_mutex_unlock(&tpool->work_lock);
    work->routine(work->arg);
  }
  return NULL;
}

When threads are created, they are waiting until a work is pushed into the work queue. In this example, I assume there is only one work in the queue. The problem here is that the work routine has only one argument, although this argument could contain multiple arguments if it is a struct in C. If an application is given, we can easily define such a struct, but since it is a library, it should handle arbitrary applications. The question is how to handle an arbitrary routine which has unknown number of arguments?

For instance, I would like the implementation be like this:

typedef struct
{
  void* (*routine)(void*); // work routine
  void** args; // point to a list of arguments
  int num_args; // number of arguments
} _work;

and in the thread_routine() , it should launch

work->routine(work->args[0], work->arg[1], work->arg[2], ...);

The point here is that we do not know what routine the thread will execute. It could be any routine. For example, the routine here could be:

work_1(int a);

work_2(int a, double b)

work_3(float* a, int c, double* b);

Any suggestions on how to achieve this goal? Thanks.

Take a look at va_arg . Its how functions like printf() can take an arbitrary number of arguments. Starting in C99, you had the ability to copy va_list using va_copy() . So I am guessing that if you can create a queuework() function that takes a variable number of arguments, you then can copy the argument list into a structure using va_list and then unroll it when its time to dispatch it.

There are several ways to do it:

You can require that your routines have void* work(void *arg) routine. If client needs to pass more than one argument, one can pack arguments into struct and pass a pointer to that struct:

struct add_ctx {
    int a, b;
    int result;
}
void *work_add (void *data)
{
    struct add_ctx *args = data;
    args->result = args->a + args->b;
}
/* Queue work order */
struct add_ctx args = { 2, 4, 0 }; // or better allocate on heap
queue_work (work_add, &add_ctx);
// wait for finish job
printf ("result: %d\n", args.result);

OR

You can create function "calling interfaces" descriptions for anticipated function signatures:

enum Type { INT, INTPTR };
struct ParamDesc {
    enum Type *param_types;
    int n_params;
    void *(*marshaller)(void);
};
/* Marshaller for void* f(int,int,int*) */
void *call_INT_INT_INTPTR (void *(*fn)(int,int,int*), void *opaque_args) {
    int a = unpack_int(opaque_args);
    int b = unpack_int(opaque_args);
    int *c = unpack_pointer(opaque_args);
    return fn(a,b,c);
}
/* Marshallers for other types... */

struct ParamDesc desc = {{INT,INT,INTPTR}, 3, call_INT_INT_INTPTR};

void *work_add (int a, int b, int *result)
{
    *result = a + b;
}

/* Queue work order */
int a,b, result;
queue_work(&desc, work_add, a, b, &result);
/* queue_work is variable arguments function that has to somehow
  pack arguments according to ParamDesc it receives */

This reuires fairly big amount of boilerplate code, however most of it can be autogenerated. See how it's done in Glib: https://developer.gnome.org/gobject/stable/signal.html

OR

You could probably use library like libffi

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM