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:
- When a new worker thread is created, a context is allocated, which the thread uses
to do its work.
- The thread then calls the blocking function.
This function blocks until the thread has work to do.
For example, the blocking function could call
MsgReceive()
to wait for a message.
- After the blocking function returns, the worker thread calls the handler function, which
performs the actual work.
- When the handler function returns, the thread calls the blocking function again.
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:
- the functions to call when a new thread is started or one dies,
in order to allocate and free contexts used by threads
- blocking and handler functions
- parameters of the thread pool, such as the number of worker threads
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 |