Thread Pools
A thread pool is a common multi-threaded design pattern. A thread pool consists of several worker threads that wait for jobs to perform. When a job is sent to the thread pool, one of the available worker threads processes it. If all worker threads are busy, either additional worker threads are created to handle the incoming jobs, or the jobs are queued until worker threads are available.
The tpool namespace of the Thread extension provides several commands for creating and managing thread pools. Using these commands is much easier than trying to build your own thread pools from scratch using mutexes, condition variables, etc. Thread pool support was added to the Thread extension in version 2.5.
The tpool::create command creates a thread pool, returning the ID of the new thread pool. There are several options to tpool::create that allow you to configure the behavior of the thread pool. The -minthreads option specifies the minimum number of threads in the pool. This number of threads is created when the thread pool is created, and as worker threads in the pool terminate, new worker threads are created to bring the number up to this minimum. The -maxthreads option specifies the maximum number of worker threads allowed. If a job is posted to the thread pool and there are no idle worker threads available, a new worker thread is created to handle the job only if the number of worker threads won't exceed the maximum number. If the maximum has been reached, the job is queued until a worker thread is available. The -idletime option specifies the number of seconds that a worker thread waits for a new job before terminating itself to preserve system resources. And the -initcmd and -exitcmd options provide scripts to respectively initialize newly created worker threads and clean up exiting worker threads.
Once you have created a thread pool, you send jobs to it with the tpool::post command. A job consists of an arbitrary Tcl script to execute. The job is executed by the first available worker thread in the pool. If there are no idle worker threads, a new worker thread is created, as long as the number of worker threads doesn't exceed the thread pool maximum. If a new worker thread can't be created, the tpool::post command blocks until a worker thread can handle the job, but while blocked the posting thread still services its event loop.
The return value of tpool::post is a job ID. To receive notification that a job is complete, your thread must call tpool::wait. The tpool::wait command blocks, but continues to service the thread's event loop while blocked. Additionally, the tpool::wait command can wait for several jobs simultaneously, returning when any of the jobs are complete. The return value of tpool::wait is a list of completed job IDs.
After tpool::wait reports that a job is complete, you can call tpool::get to retrieve the result of the job, which is the return value of the last command executed in the job script. If the job execution resulted in an error, the error is "reflected" to the posting thread: tpool::get raises an error and the values of errorInfo and errorCode are updated accordingly.
Finally, a thread pool can be preserved and released in much the same way as an individual thread. Each thread pool maintains an internal reference count, which is initially set to 0 upon creation. Any thread can increment the reference count afterwards by executing tpool::preserve, and decrement the reference count by executing tpool::release. If a call to tpool::release results in a reference count of 0 or less, the thread pool is marked for termination. Any further reference to a thread pool once it is marked for termination results in an error.
 |