Pools are usually implemented as a number of threads waiting on a producer-consumer queue for task items. The tasks are objects derived from a Task class that has a 'run()' method. Whichever thread gets a task, it calls run() and, when that returns, the thread loops round to get another task object from the queue.
This blows away any thread micro-management and works reliably and safely on every system/language I've ever tried it on.
The most flexible way I know for completion notification is for the thread to call an 'OnComplete' event, or possibly a virtual 'completed' method, of th task when run() returns, just before looping back to get the next task, with the task as a parameter. This method/event could, for example, signal an event/condvar/sema upon which the task-originating thread is waiting, could queue the completed task to the originating thread or another thread or even just delete() the task, (maybe it's job is completely done in the thread pool).
For error notification, I catch any uncaught exceptions thrown by run() and store the exception in a tast field before calling the completion method/event.
There are no locks except those protecting the producer-consumer queue, (and they are only taken for long enough to push/pop a *task).
Whichever design you use, please, please try very hard to not:
1) continually create/terminate/destroy threads - avoidable overhead and tricky to manage
2) wait with Join() for any thread to terminate - just don't :)
3) loop around some 'poll thread status' to see if they're finished yet - gets it wrong
4) Move 'working/finished' threads into and out of containers with complicated locks - deadlock-in-the-making
5) use any other sort of micro-management - difficult, messy, error-prone, too many locks, unnecesary, avoidable
The ideal thread pool is where you have no idea which thread did the work. In fact, there is usually no need even to keep any reference to the threads. Two-line pseudo threadPool:
TblockingQueue *inQueue=new TblockingQueue();
for(int i=0;i<CpoolDepth,i++) new Thread(inQueue);