thread_pool_create()

Updated: April 19, 2023

Create a thread pool handle

Synopsis:

#include <sys/iofunc.h>
#include <sys/resmgr.h>
#include <sys/dispatch.h>

thread_pool_t * thread_pool_create (
                  thread_pool_attr_t * pool_attr,
                  unsigned flags );

Arguments:

pool_attr
A pointer to a thread_pool_attr_t structure that specifies the attributes that you want to use for the thread pool. For more information, see Thread-pool attributes,” below.
flags
Flags (defined in <sys/dispatch.h>) that affect what happens to the thread that's creating the pool:
  • POOL_FLAG_CALL_BLOCK_ERRF — call the error function on an error returned by the blocking function (i.e. if the blocking function returns NULL).
  • POOL_FLAG_CALL_HANDLE_ERRF — call the error function on an error returned by the handler function (i.e. if the handler function returns -1).
  • POOL_FLAG_EXIT_SELF — when the pool is started, exit the thread that called thread_pool_start().
  • POOL_FLAG_USE_SELF — when the pool is started, use the thread that called thread_pool_start() as part of the pool.

If neither POOL_FLAG_EXIT_SELF nor POOL_FLAG_USE_SELF is set, the thread_pool_start() function will return, with new threads being created as required.

CAUTION:
If a process sets either POOL_FLAG_CALL_BLOCK_ERRF or POOL_FLAG_CALL_HANDLE_ERRF but doesn't supply an error function, and such an error occurs, a worker thread will terminate the process via the abort() function.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

The thread_pool_create() function creates a thread pool handle, which you can then pass to thread_pool_start() in order to start a thread pool. You can use these functions to create and manage a pool of worker threads.

The worker threads work in the following way:

The thread continues to block and handle events until the thread pool decides this worker thread is no longer needed. Finally, when the worker thread exits, it releases the allocated context.

The thread pool manages these worker threads so that there's a certain number of them in the blocked state. Thus, as threads become busy in the handler function, the thread pool creates new threads to keep a minimum number of threads in a state where they can accept requests from clients. By the same token, if the demand on the thread pool goes down, the thread pool lets some of these blocked threads exit.

Thread-pool attributes

The pool_attr is a pointer to a thread_pool_attr_t structure in which you specify:

Note: The thread_pool_create() function does a shallow copy of the thread_pool_attr_t structure; it doesn't make copies of anything that the structure points to. This means that items that the structure points to (e.g., the tid_name string and the pthread_attr_t structure) must exist for the lifetime of the thread pool itself. For example, this is valid:
pool_attr.tid_name = "fsys_resmgr";

but using a local or automatic variable like this isn't:

{
   char  name[32];
   snprintf(name, sizeof(name), "cam %d:%d", cam.path, cam.target);
   pool_attr.tid_name = name;
   ...
   return;
}

The thread_pool_attr_t structure is defined in <sys/dispatch.h> as:

typedef struct _thread_pool_attr {
    THREAD_POOL_HANDLE_T    *handle;
    THREAD_POOL_PARAM_T     *(*block_func)
                                (THREAD_POOL_PARAM_T *ctp);
    void                    (*unblock_func)
                                (THREAD_POOL_PARAM_T *ctp);
    int                     (*handler_func)
                                (THREAD_POOL_PARAM_T *ctp);
    THREAD_POOL_PARAM_T     *(*context_alloc)
                                (THREAD_POOL_HANDLE_T *handle);
    void                    (*context_free)
                                (THREAD_POOL_PARAM_T *ctp);
    pthread_attr_t          *attr;
    unsigned short          lo_water;
    unsigned short          increment;
    unsigned short          hi_water;
    unsigned short          maximum;
    const char              *tid_name;
    void                    (*error_func)
                                (unsigned flags, int err_value);
    unsigned                reserved[5];
} thread_pool_attr_t;
Note: In order to correctly define THREAD_POOL_PARAM_T, #include <sys/resmgr.h> before <sys/dispatch.h>.

The members include:

handle
A handle that gets passed to the context_alloc function.
block_func
The function that's called when a worker thread is ready to block, waiting for work. This function returns the same type of pointer that's passed to handler_func. If a non-NULL pointer is returned, it could differ from the one passed in, as the ctp object might have changed. If this happens, the old ctp is no longer valid.

This function may return NULL to report an error, but if NULL is returned for any reason, the old pointer must still be valid.

unblock_func
The function that's called to unblock threads. If you use dispatch_block() as the block_func, use dispatch_unblock() as the unblock_func.
Note: If you use dispatch_unblock() as the unblock_func, calling thread_pool_destroy() usually results in multiple handler_func failures, since each thread is unblocked by an unhandled pulse.
handler_func
This function is called after block_func returns to do some work and is passed the pointer returned by block_func. This function may return -1 to report an error.
context_alloc
The function that's called when a new thread is created by the thread pool. It is passed handle. The function returns a pointer, which is then passed to the blocking function, block_func. This function may return NULL to report an error.
context_free
The function that's called when a worker thread exits, to free the context allocated with context_alloc. The actual call to this function can happen after thread_pool_destroy() returns, so the function code must not reference thread pool memory, which could have been freed.
attr
A pointer to a pthread_attr_t structure (see pthread_attr_init()) that's passed to pthread_create() to specify the stack size, priority, etc. of the worker threads. If this member is NULL, default values are used.
lo_water
The minimum number of threads that the pool should keep in the blocked state (i.e., threads that are ready to do work).
increment
The number of new threads created at one time.
hi_water
The maximum number of threads to keep in a blocked state.
maximum
The maximum number of threads that the pool can create.
tid_name
NULL, or a pointer to a null-terminated name for the threads in the pool. If set, this string is passed to pthread_setname_np() when the thread pool creates a new thread.
error_func
The worker thread always calls this function when the context_alloc function fails. It also calls this function when the block_func or handler_func functions fail, provided the appropriate flags values have been passed to thread_pool_create().
When calling this function, the worker thread passes a flags field that indicates which functions failed; it also passes the errno value after those functions have returned as err_value.
Here are the flags values that the worker thread could pass to error_func:
POOL_ERROR_BLOCK
The block_func failed.
POOL_ERROR_CONTEXT
The context_alloc failed.
POOL_ERROR_HANDLER
The handler_func failed.

Returns:

A thread pool handle, or NULL if an error occurs (errno is set).

Errors:

ENOMEM
Insufficient memory to allocate internal data structures.

Examples:

Here's a simple, multithreaded resource manager:

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <sys/neutrino.h>

#define DEV_COUNT 3

static resmgr_connect_funcs_t   connect_funcs;
static resmgr_io_funcs_t        io_funcs;
static iofunc_attr_t            attrs[DEV_COUNT];

int
main(int argc, char **argv) {
    thread_pool_attr_t    pool_attr;
    thread_pool_t         *tpp;
    dispatch_t            *dpp;

    if((dpp = dispatch_create()) == NULL) {
        fprintf( stderr,
           "%s: Unable to allocate dispatch handle.\n",
           argv[0] );
        return EXIT_FAILURE;
    }

    memset( &pool_attr, 0, sizeof pool_attr );
    pool_attr.handle = dpp;
    pool_attr.context_alloc = (void *) dispatch_context_alloc;
    pool_attr.block_func = (void *) dispatch_block;
    pool_attr.unblock_func = (void *) dispatch_unblock;
    pool_attr.handler_func = (void *) dispatch_handler;
    pool_attr.context_free = (void *) dispatch_context_free;
    pool_attr.lo_water = 2;
    pool_attr.hi_water = 4;
    pool_attr.increment = 1;
    pool_attr.maximum = 50;
    pool_attr.tid_name = "my_thread_pool";

    if((tpp = thread_pool_create( &pool_attr,
                 POOL_FLAG_EXIT_SELF)) == NULL ) {
        fprintf(stderr,
                "%s: Unable to initialize thread pool.\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    iofunc_func_init( _RESMGR_CONNECT_NFUNCS,
                      &connect_funcs,
                      _RESMGR_IO_NFUNCS, &io_funcs );
    for(int i = 0; i < DEV_COUNT; i++ ) {
        char attach_path[64];
        iofunc_attr_init( &attrs[i], S_IFNAM | 0666, 0, 0 );
        sprintf(attach_path, "/dev/mydevice%d", i );
        if(resmgr_attach( dpp, NULL, attach_path, _FTYPE_ANY,
                          0, &connect_funcs, &io_funcs,
                          &attrs[i] ) == -1) {
            fprintf( stderr,
                     "%s: Unable to attach name.\n", argv[0] );
            return EXIT_FAILURE;
        }

    }

    /* Never returns */
    thread_pool_start( tpp );
    return EXIT_SUCCESS;
}

For more examples using the dispatch interface, see dispatch_create(), message_attach(), and resmgr_attach(). For more information on designing and implementing multithreaded resource managers, see the Multithreaded Resource Managers chapter of Writing a Resource Manager.

Classification:

QNX Neutrino

Safety:  
Cancellation point No
Interrupt handler No
Signal handler No
Thread Yes