This API implements a task parallelization mechanism based on the thread pool pattern.
The thread pool pattern allows to run parallel tasks easily without the burden of thread management.
The user:
- declares a thread pool and chooses the maximum number of parallel workers in charge of the execution of tasks (
threadpool_create_and_start ()
) ; - submits tasks to the thread pool (
threadpool_add_task ()
) that will be passed to one of available workers and will be executed asynchronously ; - waits for all the tasks to be completed (
threadpool_wait_and_destroy ()
).
Interface | Implementation |
---|---|
wqm.h |
wqm.c |
Create a static and dynamic library using :
$ make libs
cc -O -fPIC -c -o wqm.o wqm.c
ar rcs libwqm.a wqm.o
libwqm.a:wqm.o:0000000000000200 R ALL_TASKS
libwqm.a:wqm.o:00000000000001f0 R LAST_TASK
libwqm.a:wqm.o:0000000000000210 R NB_CPU
libwqm.a:wqm.o:00000000000001f8 R NEXT_TASK
libwqm.a:wqm.o:0000000000000208 R SEQUENTIAL
libwqm.a:wqm.o:0000000000000eac T threadpool_add_task
libwqm.a:wqm.o:00000000000022ff T threadpool_cancel_task
libwqm.a:wqm.o:0000000000000baf T threadpool_create_and_start
libwqm.a:wqm.o:00000000000022d6 T threadpool_global_data
libwqm.a:wqm.o:0000000000002773 T threadpool_global_resource
libwqm.a:wqm.o:0000000000000b18 T threadpool_monitor_every_100ms
libwqm.a:wqm.o:00000000000008ad T threadpool_monitor_to_terminal
libwqm.a:wqm.o:000000000000262e T threadpool_set_global_resource_manager
libwqm.a:wqm.o:000000000000253f T threadpool_set_idle_timeout
libwqm.a:wqm.o:00000000000017b5 T threadpool_set_monitor
libwqm.a:wqm.o:000000000000279f T threadpool_set_worker_local_data_manager
libwqm.a:wqm.o:00000000000005c5 T threadpool_task_continuation
libwqm.a:wqm.o:000000000000162b T threadpool_task_continue
libwqm.a:wqm.o:00000000000018d1 T threadpool_wait_and_destroy
libwqm.a:wqm.o:0000000000001b1d T threadpool_worker_local_data
cc -shared -o libwqm.so wqm.o
Static library | Dynamic library |
---|---|
libwqm.a |
libwqm.so |
Function | Description |
---|---|
threadpool_create_and_start |
Creates and starts a new pool of workers |
threadpool_add_task |
Adds a task to the pool of workers |
threadpool_wait_and_destroy |
Waits for all the tasks to be done and destroy the pool of workers |
threadpool_cancel_task |
Cancels all pending tasks, the last or next submitted task, or a specific task |
threadpool_set_monitor |
Sets a user-defined function to retrieve and display monitoring information |
Those features are detailed below.
Function | Description |
---|---|
threadpool_global_data |
Gives access to the user defined shared global data of the pool of workers |
threadpool_set_worker_local_data_manager |
Defines the workers' local data manager functions |
threadpool_worker_local_data |
Gives access to the user defined local data of a worker |
threadpool_set_global_resource_manager |
Defines the resource manager functions |
threadpool_global_resource |
Gives access to the global resource of the thread pool |
threadpool_set_idle_timeout |
Modifies the idle time out (default is 0.1 s) |
Those features are detailed below.
Function | Description |
---|---|
threadpool_task_continuation |
Defines a virtual task to be processed after the response of an asynchronous call |
threadpool_task_continue |
Callback function to be called by the callback function of an asynchronous call to proceed a virtual task |
Those features are detailed below.
This implementation of a thread pool brings unique features, not found anywhere else at the time of writing.
- It uses the standard (minimalist) C11 thread library <threads.h>, rather the POSIX threads. It can therefore be ported more easily to systems other than unix-like systems.
- The data passed to tasks (via
threadpool_add_task ()
) can be accessed, retrieved and released multi-thread-safely after completion of the task (via the user defined functionjob_delete ()
), allowing collecting data at task termination. - Global data can be defined and accessed (via
threadpool_global_data ()
) by all tasks. - Local data can be defined (via
threadpool_set_worker_local_data_manager ()
) and accessed (viathreadpool_worker_local_data ()
) for each worker of the thread pool. - Global resources can be allocated and deallocated (via
threadpool_set_global_resource_manager ()
) and accessed (viathreadpool_global_resource ()
) for the thread pool. - Workers will stay alive for a short idle time, ready to process new submitted tasks, even though
threadpool_wait_and_destroy ()
has already been called and no tasks are available, as long as some other tasks are still being processed and could therefore create new tasks dynamically. - The activity of the thread pool can be monitored and displayed by a front-end user defined function (via
threadpool_set_monitor ()
). - Pending tasks can be canceled after submission (via
threadpool_cancel_task ()
). - The thread pool can wait for asynchronous calls without blocking workers (via
threadpool_task_continuation ()
andthreadpool_task_continue ()
).
Those features are detailed below.
The API is defined in file wqm.h
.
struct threadpool *threadpool_create_and_start (size_t nb_workers,
void *global_data)
A thread pool is declared and started with a call to threadpool_create_and_start ()
.
The first argument nb_workers
is the number of requested workers, that is the maximum number of tasks that should be executed in parallel by the system.
NB_CPU
can be used as first argument to fit to the number of processors currently available in the system (as returned byget_nprocs ()
with GNU C standard library).SEQUENTIAL
can be used as first argument to create a sequential thread pool: tasks will be executed asynchronously, one at a time, and in the order there were submitted.
The maximum number of workers can be defined to a higher value than the number of CPUs as workers will be started only when solicited and will be released when unused after an idle time.
Nevertheless, the actual number of workers will be limited by the operating system to a lower value than nb_workers
.
- The second argument
global_data
, if not null, is a pointer to global data. This pointer can next be retrieved by tasks with the functionthreadpool_global_data ()
.
size_t threadpool_add_task (struct threadpool *threadpool,
int (*work) (struct threadpool * threadpool, void *job),
void *job,
void (*job_delete) (void *job))
A task is submitted to the thread pool with a call to threadpool_add_task ()
.
- The first argument
threadpool
is a thread pool returned by a previous call tothreadpool_create_and_start ()
. - The second argument
work
is a user defined function to be executed by a worker of the thread pool onjob
. This functionwork
should returnEXIT_SUCCESS
on success,EXIT_FAILURE
otherwise.work
should be made thread-safe as severalwork
will be running in parallel (that's the whole point of the thread pool). This function receives the thread poolthreadpool
and the jobjob
as arguments, as they were passed tothreadpool_add_task
. Therefore,work
can itself (multi-thread-safely) callthreadpool_add_task
if needed. - The third argument
job
is a pointer to the data to be used by the task and that will be processed bywork
. It should be fully initialized before it is passed tothreadpool_add_task
. It can be allocated automatically, statically or dynamically, as long it survives until the job is done bywork
.
The function threadpool_add_task
returns a unique id of the submitted task, or 0 on error (with errno
set to ENOMEM
).
- The fourth argument
job_delete
, if not null, is a user defined function called at termination of the task (after processing or cancellation.) This function receives thejob
of the task as an argument.
job_delete
should be used if the job was allocated dynamically in order to release and destroy the data after work
is done and avoid memory leaks.
job_delete
is called in a multi-thread-safe manner and can therefore safely aggregate results to those of previous tasks for instance
(in a map and reduce pattern for instance). See below.
job_delete
could as well be called manually (rather than passed as an argument tothreadpool_add_task
) at the very end ofwork ()
, but it then would not be executed multi-thread-safely, forbidding any aggregation.
If job
was allocated with a single malloc ()
and affiliated functions, free ()
is a possible choice for job_delete
.
Besides simply releasing data job
, the user-defined job_delete
can also be used as a callback function, called after the task is done or canceled, to retrieve the used and possibly modified job
and take actions (displaying, computing, aggregating, ...).
For instance, a user-defined type job_t
could be declared as a structure containing:
typedef struct {
struct { ... } input;
struct { ... } result;
int done;
} job_t;
A job
of type job_t
would then be passed to threadpool_add_task
.
The task post-processing pattern stands in 3 steps :
- A
job
is allocated and initialized before it is passed tothreadpool_add_task
:input
data is set ;done
is set to 0 ;
- In the user-defined function
work
:- the previously set
input
data is used to compute theresult
data of the task ; done
is set to 1 (and therefore remains to 0 if the task was canceled bythreadpool_cancel_task
) ;
- the previously set
- In the user-defined function
job_delete
:- if
done
is set to 1, an aggregated result of all the tasks can then be multi-thread-safely updated fromresult
;global_data
, passed tothreadpool_create_and_start
and retrievable bythreadpool_global_data
, is a possible choice to hold the aggregated result ; - any required deallocation of
job
is done.
- if
See below for the example fuzzy words of such a pattern.
Global and local data of threads can be retrieved and updated safely in the context of working threads.
The global data of a thread pool (global_data
as passed to threadpool_create_and_start
):
- should be fully initialized before
threadpool_create_and_start
is called ; - should survive after
threadpool_wait_and_destroy
is called ; - can be accessed inside user-defined functions
make_local
,delete_local
(as passed tothreadpool_create_and_start
),work
andjob_delete
(as passed tothreadpool_add_task
), anddeallocator
(as passed tothreadpool_set_resource_manager
) with :
void *threadpool_global_data (void)
threadpool_global_data
can not be called in the resourceallocator
set bythreadpool_set_resource_manager
.
The local data of a worker (created and returned by make_local
and destroyed by delete_local
, as passed to threadpool_set_worker_local_data_manager
) can be accessed inside user-defined functions work
and job_delete
(as passed to threadpool_add_task
) with :
void threadpool_set_worker_local_data_manager (struct threadpool *threadpool, void *(*make_local) (void), void (*delete_local) (void *local_data))
void *threadpool_worker_local_data (void)
These two functions must be called in the context of a running thread, otherwise they return 0.
size_t threadpool_cancel_task (struct threadpool *threadpool, size_t task_id)
Previously submitted and still pending tasks can be canceled.
task_id
is:
- either a unique id returned by a previous call to
threadpool_add_task
; - or
ALL_TASKS
to cancel all still pending tasks ; - or
NEXT_TASK
to cancel the next still pending submitted task (it can be used several times in a row) ; - or
LAST_TASK
to cancel the last still pending submitted task (it can be used several times in a row).
Canceled tasks won't be processed, but job_delete
, as optionally passed to threadpool_add_task
, will be called though.
The function returns the number of canceled tasks, if any, or 0 if there are not any left pending task to be canceled.
void threadpool_wait_and_destroy (struct threadpool *threadpool)
The single argument threadpool
is a thread pool returned by a previous call to threadpool_create_and_start ()
.
This function declares that all the tasks have been submitted. It then waits for all the tasks to be completed by workers.
threadpool
should not be used after a call to threadpool_wait_and_destroy ()
.
A monitoring of the thread pool activity can optionally be activated by calling
void threadpool_set_monitor (struct threadpool *threadpool, threadpool_monitor_handler handler, void *arg, threadpool_monitor_filter filter)
A user-defined handler
function is passed as second argument, with signature
void (*threadpool_monitor_handler) (struct threadpool_monitor, void *arg)
0
or a user-defined filter
function can be passed as fourth argument, with signature
int (*threadpool_monitor_filter) (struct threadpool_monitor d)
The handler
will be called to retrieve and display information about the activity of the thread pool.
It :
- will be called whenever the state of the thread pool changes and, if
filter
is not null, wheneverfilter
returns non-zero.filter
should be set to 0 to monitor every change of the thread pool state. - will be passed the argument
arg
(which can be0
) previously passed tothreadpool_set_monitor
as thirdargument, - will be called asynchronously, without interfering with the execution of workers (actually, a sequential dedicated asynchronous thread pool is used for monitoring),
- will be executed multi-thread-safely,
- should not be called after
threadpool_wait_and_destroy
has been called.
The monitoring data are passed to the handler function in a structure threadpool_monitor
which contains:
struct threadpool *threadpool
: the thread pool for which the monitoring handler is called ;double time
: the elapsed seconds since the creation of the thread pool (threadpool_create_and_start
) ;int closed
: 1 if the thread pool has been closed (by a call tothreadpool_wait_and_destroy
), 0 otherwise ;size_t workers.nb_requested
: the requested number of workers, as defined at the creation of the thread pool ;size_t workers.nb_max
: the maximum number of workers granted by the operating system (<=workers.nb_requested
) ;size_t workers.nb_alive
: the number of alive workers, either running (tasks.nb_processing
) or waiting (workers.nb_idle
) ;size_t workers.nb_idle
: the number of idle worker, i.e. waiting (some time) for a task to process ;size_t tasks.nb_pending
: the number of tasks submitted to the thread pool and not yet processed or being processed ;size_t tasks.nb_processing
: the number of running workers, i.e. processing a task ;size_t tasks.nb_asynchronous
: the number of asynchronous (virtual) tasks ;size_t tasks.nb_succeeded
: the number of already processed and succeeded tasks by the thread pool (a task is considered successful whenwork
, the function passed tothreadpool_add_task
, returns 0) ;size_t tasks.nb_failed
: the number of already processed and failed tasks by the thread pool (a task is considered failed whenwork
, the function passed tothreadpool_add_task
, does not return 0) ;size_t tasks.nb_canceled
: the number of canceled tasks ;size_t tasks.nb_submitted
: the number of submitted tasks (either pending, processing, succeeded, failed or canceled).
A handler threadpool_monitor_to_terminal
is available for convenience:
- It can be used as the second argument of
threadpool_set_monitor
. - It displays monitoring data as text sent to a stream of type
FILE *
, passed as the third argument ofthreadpool_set_monitor
.stderr
will be used by default if this third argument isNULL
.
A filter threadpool_monitor_every_100ms
is available for convenience:
- It can be used as the fourth argument of
threadpool_set_monitor
. - It permits to monitor not more often than every 100 ms.
Data used in the context of a thread pool can be managed globally or locally with four different ways, depending on the scope and life-cycle of the data.
Scope | Access | Management |
---|---|---|
Thread pool | threadpool_global_data |
threadpool_create_and_start , see Manage global data |
Active thread pool (running workers) | threadpool_global_resource |
threadpool_set_global_resource_manager , see Manage global resources |
Worker | threadpoolworker_local_data |
threadpool_set_worker_local_data_manager , see Manage worker local data |
Task | job in work |
threadpool_add_task , see Manage task data |
A global context data of a thread pool can be passed as second argument when it is created with threadpool_create_and_start
.
This data should be allocated (statically, automatically or dynamically) before calling threadpool_create_and_start
and respectively deallocated after calling threadpool_wait_and_destroy
.
This global data of a thread pool can be accessed inside:
- user-defined functions
allocator
anddeallocator
passed tothreadpool_set_global_resource_manager
; - user-defined functions
make_local
anddelete_local
(as passed tothreadpool_set_worker_local_data_manager
) ; - user-defined functions
work
andjob_delete
(as passed tothreadpool_add_task
) with :
void *threadpool_global_data (void)
In case external resources should be allocated for tasks processing (for instance a shared connection to a database), user-defined functions allocator
and deallocator
can be set with:
void threadpool_set_global_resource_manager (struct threadpool *threadpool, void *(*allocator) (void *global_data), void (*deallocator) (void *resource))
This function should be called after
threadpool_create_and_start
and before adding tasks to the thread poll withthreadpool_add_task
. Otherwise, it would have no effect (as workers are already running) anderrno
would be set toECANCELED
.
- The user-defined function
allocator
:- should fully initialize and return the global resource of the thread pool ;
- is passed the
global_data
of the thread pool as an argument ; - will generally be called once per thread pool, before processing the very first task.
- The user-defined function
deallocator
:- should fully release the global resource of the thread pool ;
- is passed the resource to deallocate, as previously returned by
allocator
, as an argument ; - will generally be called once per thread pool, after all tasks have been processed or canceled.
Moreover, if the thread pool remains idle (waiting for tasks to process) for too long (see below), resources will be deallocated automatically, and will be reallocated automatically when the thread pool gets alive again.
The global resource of a thread pool (as returned by the allocator
passed to threadpool_set_global_resource_manager
) can be accessed inside user-defined functions make_local
, delete_local
(as passed to threadpool_set_worker_local_data_manager
), work
and job_delete
(as passed to threadpool_add_task
) with :
void *threadpool_global_resource (void)
The timeout delay before thread pool internal and external resources are deallocated when a thread pool is kept idle for too long can be modified with:
void threadpool_set_idle_timeout (struct threadpool *threadpool, double delay)
delay
, in seconds, should be a non negative value, otherwise it is ignored and errno
is set to EINVAL
.
It can not exceed 10.000.000 seconds.
This delay should be set to a value larger than the time required to allocate global resources for tasks. It should nevertheless be kept as low as possible to release unused scarce resources when workers are kept idle for a long period.
The default delay is 0.1 seconds when a thread pool is created by threadpool_create_and_start
.
The delay is set to 10.000.000 seconds by default after threadpool_set_global_resource_manager
is called : resources will not be deallocated by default if the thread pool is idle.
threadpool_set_idle_timeout
should be called after threadpool_set_global_resource_manager
to lower the delay in order to deallocate scarce resources after a specified idle delay.
In case resources should be allocated for each worker (for instance a connection to a database), user-defined functions make_local
and delete_local
can be set with:
void threadpool_set_worker_local_data_manager (struct threadpool *threadpool, void *(*make_local) (void), void (*delete_local) (void *local_data));
This function should be called after
threadpool_create_and_start
and before adding tasks to the thread poll withthreadpool_add_task
. Otherwise, it would have no effect (as workers are already running) anderrno
would be set toECANCELED
.
make_local
will be called when a worker is created and delete_local
when it is terminated:
- The argument
make_local
, if not null, is a user defined function that returns a pointer to data for a local usage by a worker. This pointer can next be retrieved by tasks with the functionthreadpool_worker_local_data ()
.make_local
is called in a multi-thread-safe manner at the initialization of a worker and is passed the pointer to global data.make_local
can therefore safely access (and update) the content ofglobal_data
if needed. - The argument
delete_local
, if not null, is a user defined function that is executed to release and destroys the local data used by each worker (passed as an argument) when a worker stops.delete_local
is called in a multi-thread-safe manner at the termination of a worker and is passed the pointer to global data.delete_local
can therefore safely access (and update) the content ofglobal_data
if needed (to gather results or statistics for instance).
The local data of a worker (as returned by the make_local
passed to threadpool_set_worker_local_data_manager
) can be accessed inside user-defined functions work
and job_delete
(as passed to threadpool_add_task
) with :
void *threadpoolworker_local_data (void)
The data of a job
is passed to a task when it is added to a thread pool with threadpool_add_task
.
See Submit a task.
In case a task would need to use an asynchronous call, the continuation of the task work_continuator
can be specified by calling
uint64_t threadpool_task_continuation (int (*work_continuator) (struct threadpool * threadpool, void *data), double seconds)
just before the asynchronous call.
The function work_continuator
indicates the function to be called when the asynchronous call notifies its completion.
work_continuator
should return EXIT_SUCCESS
on success, EXIT_FAILURE
otherwise.
threadpool_task_continuation
returns 0 and setserrno
toEINVAL
ifwork_continuator
is null.threadpool_task_continuation
returns 0 and setserrno
toEPERM
if it is called outside of:- a function
work
of a task scheduled withthreadpool_add_task
; - a function
work_continuator
of a task scheduled withthreadpool_task_continuation
;
- a function
- otherwise
threadpool_task_continuation
returns a non-zero unique ID that should be passed to the asynchronous call.
seconds
is a timeout delay, in seconds, for the asynchronous response. After this delay, the asynchronous response will be ignored (see below).
In the callback function of the asynchronous call (when the asynchronous call notifies its completion), the function
int threadpool_task_continue (uint64_t uid)
should be called with the ID previously retrieved to continue to process the initial task.
The function work_continuator
previously declared by threadpool_task_continuation
will then be automatically called:
- If
threadpool_task_continue
is called before the timeout delayseconds
has elapsed,work_continuator
will be scheduled by the thread pool andEXIT_SUCCESS
will be returned. - If
threadpool_task_continue
is called after the timeout delayseconds
has elapsed,threadpool_task_continue
will have no effect, will returnEXIT_FAILURE
anderrno
will be set toETIMEOUT
.
The task does not block any worker between the calls to threadpool_task_continuation
and threadpool_task_continue
.
Workers are available to process any other tasks (either asynchronous or not): the tasks using threadpool_task_continuation
and threadpool_task_continue
behave like virtual tasks.
This features was inspired from Java virtual threads (see https://openjdk.org/jeps/444).
Type make
to compile and run the examples (in sub-folder examples).
An example of the usage of thread pool is given in files qsip_wc.c
and qsip_wc_test.c
here.
It sorts 2 bunches of 50 lists of 1.000.000 numbers.
Two encapsulated thread pools are used : one to distribute 100 tasks over 7 monitored threads, each task sorting 1000000 numbers distributed over the CPU threads.
-
qsip_wc.c
is an attempt to implement a parallelized version of the quick sort algorithm (using a thread pool);- It uses features such as global data, worker local data, dynamic creation and deletion of jobs.
- It reveals that a parallelized quick sort is inefficient due to thread management overhead.
-
qsip_wc_test.c
is an example of a thread pool that sorts several arrays using the above parallelized version of the quick sort algorithm.- It uses features such as global data, worker local data, task cancellation, (fake) resource management and monitoring.
Running this example with
$ make qsip_wc_test
yields:
Sorting 1,000,000 elements (multi-threaded quick sort in place), 100 times:
Initializing 100,000,000 random numbers...
7 workers requested and processing...
Allocate resources...
(=) succeeded tasks, (X) failed tasks, (*) processing tasks, (.) pending tasks, (/) canceled tasks, (~) idle thread pool.
[0x5cbba68eb7d0 (7)][ 0.0000s][ 0]
[0x5cbba68eb7d0 (7)][ 0.0002s][ 1] .
Resources allocated.
[0x5cbba68eb7d0 (7)][ 2.0006s][ 1] .
[0x5cbba68eb7d0 (7)][ 2.0010s][ 2] ..
Will go to sleep for 16 seconds...
[0x5cbba68eb7d0 (7)][ 2.0012s][ 3] ...
[0x5cbba68eb7d0 (7)][ 2.0014s][ 4] ....
...
[0x5cbba68eb7d0 (7)][ 13.1516s][ 50] =================================================*
[0x5cbba68eb7d0 (7)][ 13.2413s][ 50] ================================================== ~
[0x5cbba68eb7d0 (7)][ 14.1519s][ 50] ================================================== ~
Deallocate resources...
[0x5cbba68eb7d0 (7)][ 14.2414s][ 50] ==================================================
Resources deallocated.
[0x5cbba68eb7d0 (7)][ 16.2417s][ 50] ==================================================
Stop sleeping after 16 seconds.
Allocate resources...
[0x5cbba68eb7d0 (7)][ 18.0050s][ 51] ==================================================.
Resources allocated.
[0x5cbba68eb7d0 (7)][ 20.0054s][ 51] ==================================================.
[0x5cbba68eb7d0 (7)][ 20.0056s][ 52] ==================================================..
...
[0x5cbba68eb7d0 (7)][ 24.6119s][ 100] ===============================================================*////////////////////////////////////
[0x5cbba68eb7d0 (7)][ 24.7960s][ 100] ================================================================//////////////////////////////////// ~
Deallocate resources...
Resources deallocated.
[0x5cbba68eb7d0 (7)][ 26.7966s][ 100] ================================================================////////////////////////////////////
Done.
This example matches a list of french fuzzy words against the french dictionary.
Run it with:
$ make fuzzyword
Two encapsulated thread pools are used : one to distribute the list of words on one monitored single thread (words are processed sequentially), each word being compared to the entries (distributed over the CPU threads) of the dictionary.
It uses job_delete
as a callback function for task post-processing and threadpool_set_global_resource_manager
for global resource management.
This example requests more workers than what the system permits.
Run it with:
$ make intensive
This example uses threadpool_task_continuation
and threadpool_task_continue
to create asynchronous virtual tasks.
Asynchronous tasks (a pause of one second) can be processed with a thread pool of one worker only: those tasks behave like virtual tasks which do not block the worker (awesome !)
Run it with:
$ make timers
The API is implemented in C11 (file wqm.c
) using the standard C thread library <threads.h>.
It is highly inspired from the great book "Programming with POSIX Threads" written by David R. Butenhof, 21st ed., 2008, Addison Wesley.
It has been heavily tested, but bugs are still possible. Please don't hesitate to report them to me.
Workers are started automatically when needed, that is when a new task is submitted whereas all workers are already booked. Workers are running in parallel, asynchronously. Each worker can process one task at a time. A task is processed as soon as a worker is available.
A worker stops when all submitted tasks have been processed or after an idle time. Idle workers are kept ready for new tasks for a short time and are then stopped automatically to release system resources.
For instance, say a task requires 2 seconds to be processed and the maximum idle delay for a worker is half a second:
- if the task is repeatedly submitted every 3 seconds to the thread pool, one worker will be created and activated on submission, and stopped after completion of the task and an idle time (of half a second).
- if the task is submitted every 2.4 seconds to the thread pool, one worker will be created and activated on submission, kept idle and reactivated for the next task.
- if the task is submitted every 2 seconds, one worker will be active to process the tasks.
- if the task is submitted every second, two workers will be active to process the tasks.
- if the rate of submitted tasks is very high, the maximum number of workers (as passed to
threadpool_create_and_start ()
) will be active.
Therefore, the number for workers automatically adapts to the rate and duration for tasks.
Zed is dead, but C is not.