thread_pool_create()

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_EXIT_SELF — when the pool is started using thread_pool_start(), exit the thread that called this function.
  • POOL_FLAG_USE_SELF — when the pool is started, use the calling thread as part of the pool.

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;
    unsigned                reserved[7];
} 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 the 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 be different from the one passed in, as the ctp object could be reallocated to a larger size. If this happens, the old ctp is no longer valid. However, if NULL is returned, the old pointer is still 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.
handler_func
The function that's called after block_func returns to do some work. The function is passed the pointer returned by block_func.
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.
context_free
The function that's called when the 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.

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:

/* Define an appropriate interrupt number: */
#define INTNUM 0     

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

static resmgr_connect_funcs_t   connect_funcs;
static resmgr_io_funcs_t        io_funcs;
static iofunc_attr_t            attr;

void *interrupt_thread( void *data)    
/* *data isn't used */
{
    struct sigevent event;
    int             id;

    /* fill in "event" structure */
    memset( &event, 0, sizeof(event) );
    event.sigev_notify = SIGEV_INTR;

    /* INTNUM is the desired interrupt level */
    id = InterruptAttachEvent( INTNUM, &event, 0 );
    
    …

    while (1) {
        InterruptWait( 0, NULL );
        /*
         do something about the interrupt,
         perhaps updating some shared
         structures in the resource manager

         unmask the interrupt when done
        */
        InterruptUnmask( INTNUM, id );
    }
}

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


    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 );
    iofunc_attr_init( &attr, S_IFNAM | 0666, 0, 0 );
        
    memset( &resmgr_attr, 0, sizeof resmgr_attr );
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    if((id = resmgr_attach( dpp, &resmgr_attr, 
                            "/dev/mynull", 
                           _FTYPE_ANY, 0, &connect_funcs, 
                            &io_funcs, 
                            &attr )) == -1) {
        fprintf( stderr, 
                 "%s: Unable to attach name.\n", argv[0] );
        return EXIT_FAILURE;
    }

    /* Start the thread which will handle interrupt events. */
    pthread_create ( NULL, NULL, interrupt_thread, NULL );

    /* 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 information on advanced topics in designing and implementing a resource manager, see the Combine Messages chapter of Writing a Resource Manager.

Classification:

QNX Neutrino

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