---
name: "ACE_Thread_Manager"
section: "3"
platform: "linux"
package: "libace-doc"
version: "5.0.7-4"
release: "Debian 2.2 (Potato)"
platforms: ["linux"]
url: "https://new.carta.tech/ACE_Thread_Manager"
---

# ACE\_Thread\_Manager(3)

Manages a pool of threads.

## NAME

ACE\_Thread\_Manager -  Manages a pool of threads.

## SYNOPSIS

```
#include <ace/Thread_Manager.h>
class ACE_Thread_Manager
{
  public:
    friend class ACE_Thread_Control;
    friend class ACE_Thread_Descriptor;
    typedef int (ACE_Thread_Manager::*ACE_THR_MEMBER_FUNC)(
        ACE_Thread_Descriptor *,
        int
        );
        enum{ ACE_THR_IDLE = 0x00000000,
        ACE_THR_SPAWNED = 0x00000001,
        ACE_THR_RUNNING = 0x00000002,
        ACE_THR_SUSPENDED = 0x00000004,
        ACE_THR_CANCELLED = 0x00000008,
        ACE_THR_TERMINATED = 0x00000010,
        ACE_THR_JOINING = 0x10000000 };

    ACE_Thread_Manager (
        size_t preaolloc = 0,
        size_t lwm = ACE_DEFAULT_THREAD_MANAGER_LWM,
        size_t inc = ACE_DEFAULT_THREAD_MANAGER_INC,
        size_t hwm = ACE_DEFAULT_THREAD_MANAGER_HWM
        );
    virtual ~ACE_Thread_Manager (void);
    static ACE_Thread_Manager *instance (void);
    static ACE_Thread_Manager *instance (ACE_Thread_Manager *);
    static void close_singleton (void);
    int open (size_t size = 0);
    int close (void);
    int spawn (
        ACE_THR_FUNC func,
        void *args = 0,
        long flags = THR_NEW_LWP | THR_JOINABLE,
        ACE_thread_t * = 0,
        ACE_hthread_t *t_handle = 0,
        long priority = ACE_DEFAULT_THREAD_PRIORITY,
        int grp_id = -1,
        void *stack = 0,
        size_t stack_size = 0
        );
    int spawn_n (
        size_t n,
        ACE_THR_FUNC func,
        void *args = 0,
        long flags = THR_NEW_LWP | THR_JOINABLE,
        long priority = ACE_DEFAULT_THREAD_PRIORITY,
        int grp_id = -1,
        ACE_Task_Base *task = 0,
        ACE_hthread_t thread_handles[] = 0,
        void *stack[] = 0,
        size_t stack_size[] = 0
        );
    int spawn_n (
        ACE_thread_t thread_ids[],
        size_t n,
        ACE_THR_FUNC func,
        void *args,
        long flags,
        long priority = ACE_DEFAULT_THREAD_PRIORITY,
        int grp_id = -1,
        void *stack[] = 0,
        size_t stack_size[] = 0,
        ACE_hthread_t thread_handles[] = 0
        );
    void *exit (void *status = 0, int do_thread_exit = 1);
    int wait (
        const ACE_Time_Value *timeout = 0,
        int abandon_detached_threads = 0
        );
    int join (ACE_thread_t tid, void **status = 0);
    int wait_grp (int grp_id);
    ACE_Thread_Descriptor *thread_desc_self (void);
    ACE_Thread_Descriptor *thread_descriptor (ACE_thread_t);
    ACE_Thread_Descriptor *hthread_descriptor (ACE_hthread_t);
    int thr_self (ACE_hthread_t &);
    ACE_thread_t thr_self (void);
    ACE_Task_Base *task (void);
    int suspend_all (void);
    int suspend (ACE_thread_t);
    int suspend_grp (int grp_id);
    int testsuspend (ACE_thread_t t_id);
    int resume_all (void);
    int resume (ACE_thread_t);
    int resume_grp (int grp_id);
    int testresume (ACE_thread_t t_id);
    int kill_all (int signum);
    int kill (ACE_thread_t, int signum);
    int kill_grp (int grp_id, int signum);
    int cancel_all (int async_cancel = 0);
    int cancel (ACE_thread_t, int async_cancel = 0);
    int cancel_grp (int grp_id, int async_cancel = 0);
    int testcancel (ACE_thread_t t_id);
    int set_grp (ACE_thread_t, int grp_id);
    int get_grp (ACE_thread_t, int &grp_id);
    int wait_task (ACE_Task_Base *task);
    int suspend_task (ACE_Task_Base *task);
    int resume_task (ACE_Task_Base *task);
    int kill_task (ACE_Task_Base *task, int signum);
    int cancel_task (ACE_Task_Base *task, int async_cancel = 0);
    int hthread_within (ACE_hthread_t handle);
    int thread_within (ACE_thread_t tid);
    int num_tasks_in_group (int grp_id);
    int num_threads_in_task (ACE_Task_Base *task);
    int task_list (int grp_id, ACE_Task_Base *task_list[], size_t n);
    int thread_list (
        ACE_Task_Base *task,
        ACE_thread_t thread_list[],
        size_t n
        );
    int hthread_list (
        ACE_Task_Base *task,
        ACE_hthread_t hthread_list[],
        size_t n
        );
    int thread_grp_list (
        int grp_id,
        ACE_thread_t thread_list[],
        size_t n
        );
    int hthread_grp_list (
        int grp_id,
        ACE_hthread_t hthread_list[],
        size_t n
        );
    int task_all_list (ACE_Task_Base *task_list[], size_t n);
    int thread_all_list (ACE_thread_t thread_list[], size_t n);
    int set_grp (ACE_Task_Base *task, int grp_id);
    int get_grp (ACE_Task_Base *task, int &grp_id);
    int count_threads (void) const;
    int at_exit (ACE_At_Thread_Exit* cleanup);
    int at_exit (ACE_At_Thread_Exit& cleanup);
    int at_exit (
        void *object,
        ACE_CLEANUP_FUNC cleanup_hook,
        void *param
        );
    void wait_on_exit (int dowait);
    int wait_on_exit (void);
    void dump (void);
    ACE_ALLOC_HOOK_DECLARE;
  protected:
    virtual int spawn_i (
        ACE_THR_FUNC func,
        void *args,
        long flags,
        ACE_thread_t * = 0,
        ACE_hthread_t *t_handle = 0,
        long priority = ACE_DEFAULT_THREAD_PRIORITY,
        int grp_id = -1,
        void *stack = 0,
        size_t stack_size = 0,
        ACE_Task_Base *task = 0
        );
    void run_thread_exit_hooks (int i);
    ACE_Thread_Descriptor *find_thread (ACE_thread_t t_id);
    ACE_Thread_Descriptor *find_hthread (ACE_hthread_t h_id);
    ACE_Thread_Descriptor *find_task (
        ACE_Task_Base *task,
        int slot = -1
        );
    int insert_thr (
        ACE_thread_t t_id,
        ACE_hthread_t,
        int grp_id = -1,
        long flags = 0
        );
    int append_thr (
        ACE_thread_t t_id,
        ACE_hthread_t,
        ACE_UINT32,
        int grp_id,
        ACE_Task_Base *task = 0,
        long flags = 0,
        ACE_Thread_Descriptor *td = 0
        );
    void remove_thr (ACE_Thread_Descriptor *td, int close_handler);
    void remove_thr_all (void);
    int check_state (
        ACE_UINT32 state,
        ACE_thread_t thread,
        int enable = 1
        );
    int apply_task (
        ACE_Task_Base *task,
        ACE_THR_MEMBER_FUNC,
        int = 0
        );
    int apply_grp (int grp_id, ACE_THR_MEMBER_FUNC func, int arg = 0);
    int apply_all (ACE_THR_MEMBER_FUNC, int = 0);
    int join_thr (ACE_Thread_Descriptor *td, int = 0);
    int resume_thr (ACE_Thread_Descriptor *td, int = 0);
    int suspend_thr (ACE_Thread_Descriptor *td, int = 0);
    int kill_thr (ACE_Thread_Descriptor *td, int signum);
    int cancel_thr (ACE_Thread_Descriptor *td, int async_cancel = 0);
    int register_as_terminated (ACE_Thread_Descriptor *td);
    ACE_Double_Linked_List<ACE_Thread_Descriptor> thr_list_;
        ACE_Double_Linked_List<ACE_Thread_Descriptor_Base> terminated_thr_list_;

    ACE_Unbounded_Queue<ACE_Thread_Descriptor*> thr_to_be_removed_;
    int grp_id_;
    int automatic_wait_;
    ACE_Thread_Mutex lock_;
    ACE_Condition_Thread_Mutex zero_cond_;
  private:
        ACE_Locked_Free_List<ACE_Thread_Descriptor, ACE_SYNCH_MUTEX> thread_desc_freelist_;

    static ACE_Thread_Manager *thr_mgr_;
    static int delete_thr_mgr_;
};
```

## DESCRIPTION

This class allows operations on groups of threads atomically. The default behavior of thread manager is to wait on all threads under it's management when it gets destructed. Therefore, remember to remove a thread from thread manager if you don't want it to wait for the thread. There are also function to disable this default wait-on-exit behavior. However, if your program depends on turning this off to run correctly, you are probably doing something wrong.  Rule of thumb, use ACE\_Thread to manage your daemon threads.

Notice that if there're threads live beyond the scope of main, you are sure to have resource leaks in your program. Remember to wait on threads before exiting main if that could happen in your programs.

## Initialization and termination methods.

```
ACE_Thread_Manager (
    size_t preaolloc = 0,
    size_t lwm = ACE_DEFAULT_THREAD_MANAGER_LWM,
    size_t inc = ACE_DEFAULT_THREAD_MANAGER_INC,
    size_t hwm = ACE_DEFAULT_THREAD_MANAGER_HWM
    );
```

```
virtual ~ACE_Thread_Manager (void);
```

```
static ACE_Thread_Manager *instance (void);
```

Get pointer to a process-wide ACE\_Thread\_Manager.

```
static ACE_Thread_Manager *instance (ACE_Thread_Manager *);
```

Set pointer to a process-wide ACE\_Thread\_Manager and return existing pointer.

```
static void close_singleton (void);
```

Delete the dynamically allocated Singleton

```
int open (size_t size = 0);
```

No-op.  Currently unused.

```
int close (void);
```

Release all resources. By default, this method will wait till all threads exit.  However, when called from close\_singleton, most global resources are destroyed and thus, we don't try to wait but just clean up the thread descriptor list.

The ACE\_thread\_t \* argument to each of the spawn family member functions is interpreted and used as shown in the following table.  NOTE:  the final option, to provide task names, is \_only\_ supported on VxWorks!

Value of ACE\_thread\_t \* argument  Use                         Platforms ================================  ==========================  ========= 0                                 Not used.                   All non-0 (and points to 0 char \*     The task name is passed     All\
   on VxWorks)                       back in the char \*. non-0, points to non-0 char \*     The char \* is used as       VxWorks only\
                                     the task name.  The\
                                     argument is not modified.

```
int spawn (
    ACE_THR_FUNC func,
    void *args = 0,
    long flags = THR_NEW_LWP | THR_JOINABLE,
    ACE_thread_t * = 0,
    ACE_hthread_t *t_handle = 0,
    long priority = ACE_DEFAULT_THREAD_PRIORITY,
    int grp_id = -1,
    void *stack = 0,
    size_t stack_size = 0
    );
```

Create a new thread, which executes func. Returns: on success a unique group id that can be used to control other threads added to the same group.  On failure, returns -1.

```
int spawn_n (
    size_t n,
    ACE_THR_FUNC func,
    void *args = 0,
    long flags = THR_NEW_LWP | THR_JOINABLE,
    long priority = ACE_DEFAULT_THREAD_PRIORITY,
    int grp_id = -1,
    ACE_Task_Base *task = 0,
    ACE_hthread_t thread_handles[] = 0,
    void *stack[] = 0,
    size_t stack_size[] = 0
    );
```

Create N new threads, all of which execute func. Returns: on success a unique group id that can be used to control all of the threads in the same group.  On failure, returns -1.

```
int spawn_n (
    ACE_thread_t thread_ids[],
    size_t n,
    ACE_THR_FUNC func,
    void *args,
    long flags,
    long priority = ACE_DEFAULT_THREAD_PRIORITY,
    int grp_id = -1,
    void *stack[] = 0,
    size_t stack_size[] = 0,
    ACE_hthread_t thread_handles[] = 0
    );
```

Spawn N new threads, which execute func with argument arg. If thread\_ids != 0 the thread\_ids of successfully spawned threads will be placed into the thread\_ids buffer (which must be the same size as n).  If stack != 0 it is assumed to be an array of n pointers to the base of the stacks to use for the threads being spawned.  If stack\_size != 0 it is assumed to be an array of n values indicating how big each of the corresponding stacks are.  If thread\_handles != 0 it is assumed to be an array of n thread\_handles that will be assigned the values of the thread handles being spawned.  Returns -1 on failure (errno will explain...), otherwise returns the group id of the threads.

```
void *exit (void *status = 0, int do_thread_exit = 1);
```

Called to clean up when a thread exits.  If do\_thread\_exit is non-0 then ACE\_Thread::exit is called to exit the thread, in which case status is passed as the exit value of the thread. Should \_not\_ be called by main thread.

```
int wait (
    const ACE_Time_Value *timeout = 0,
    int abandon_detached_threads = 0
    );
```

Block until there are no more threads running in the Thread\_Manager or timeout expires.  Note that timeout is treated as "absolute" time.  Returns 0 on success and -1 on failure.  If abandon\_detached\_threads is set, wait will first check thru its thread list for threads with THR\_DETACHED or THR\_DAEMON flags set and remove these threads.  Notice that unlike other wait\_\* function, by default, wait does wait on all thread spawned by this thread\_manager no matter the detached flags are set or not unless it is called with abandon\_detached\_threads flag set.

```
int join (ACE_thread_t tid, void **status = 0);
```

Join a thread specified by tid.  Do not wait on a detached thread.

```
int wait_grp (int grp_id);
```

Block until there are no more threads running in a group. Returns 0 on success and -1 on failure.  Notice that wait\_grp will not wait on detached threads.

## Accessors for ACE\_Thread\_Descriptors.

```
ACE_Thread_Descriptor *thread_desc_self (void);
```

Get a pointer to the calling thread's own thread\_descriptor. This must be called from a spawn thread.  This function will fetch the info from TSS.

```
ACE_Thread_Descriptor *thread_descriptor (ACE_thread_t);
```

Return a pointer to the thread's Thread\_Descriptor, 0 if fail.

```
ACE_Thread_Descriptor *hthread_descriptor (ACE_hthread_t);
```

Return a pointer to the thread's Thread\_Descriptor, 0 if fail.

```
int thr_self (ACE_hthread_t &);
```

Return the "real" handle to the calling thread, caching it if necessary in TSS to speed up subsequent lookups. This is necessary since on some platforms (e.g., Win32) we can't get this handle via direct method calls.  Notice that you should \*not\* close the handle passed back from this method.  It is used internally by Thread Manager.  On the other hand, you \*have to\* use this internal thread handle when working on Thread\_Manager. Return -1 if fail.

```
ACE_thread_t thr_self (void);
```

Return the unique ID of the thread.  This is not strictly necessary (because a thread can always just call ACE\_Thread::self).  However, we put it here to be complete.

```
ACE_Task_Base *task (void);
```

Returns a pointer to the current ACE\_Task\_Base we're executing in if this thread is indeed running in an ACE\_Task\_Base, else return 0.

## Suspend methods, which isn't supported on POSIX pthreads (will not block).

```
int suspend_all (void);
```

Suspend all threads

```
int suspend (ACE_thread_t);
```

Suspend a single thread.

```
int suspend_grp (int grp_id);
```

Suspend a group of threads.

```
int testsuspend (ACE_thread_t t_id);
```

True if t\_id is inactive (i.e., suspended), else false.

## Resume methods, which isn't supported on POSIX pthreads (will not block).

```
int resume_all (void);
```

Resume all stopped threads

```
int resume (ACE_thread_t);
```

Resume a single thread.

```
int resume_grp (int grp_id);
```

Resume a group of threads.

```
int testresume (ACE_thread_t t_id);
```

True if t\_id is active (i.e., resumed), else false.

## Kill methods, send signals -- which isn't supported on Win32 (will not block).

```
int kill_all (int signum);
```

Send signum to all stopped threads

```
int kill (ACE_thread_t, int signum);
```

Kill a single thread.

```
int kill_grp (int grp_id, int signum);
```

Kill a group of threads.

## Cancel methods, which provides a cooperative thread-termination mechanism (will not block).

```
int cancel_all (int async_cancel = 0);
```

Cancel's all the threads.

```
int cancel (ACE_thread_t, int async_cancel = 0);
```

Cancel a single thread.

```
int cancel_grp (int grp_id, int async_cancel = 0);
```

Cancel a group of threads.

```
int testcancel (ACE_thread_t t_id);
```

True if t\_id is cancelled, else false.

## Set/get group ids for a particular thread id.

```
int set_grp (ACE_thread_t, int grp_id);
```

```
int get_grp (ACE_thread_t, int &grp_id);
```

## The following methods are new methods which resemble current

methods in ACE\_Thread Manager. For example, the apply\_task method resembles the apply\_thr method, and suspend\_task resembles suspend\_thr.

## Operations on ACE\_Tasks.

```
int wait_task (ACE_Task_Base *task);
```

Block until there are no more threads running in task.  Returns 0 on success and -1 on failure.  Notice that wait\_task will not wait on detached threads.

```
int suspend_task (ACE_Task_Base *task);
```

Suspend all threads in an ACE\_Task.

```
int resume_task (ACE_Task_Base *task);
```

Resume all threads in an ACE\_Task.

```
int kill_task (ACE_Task_Base *task, int signum);
```

Send a signal signum to all threads in an ACE\_Task.

```
int cancel_task (ACE_Task_Base *task, int async_cancel = 0);
```

Cancel all threads in an ACE\_Task.  If async\_cancel is non-0, then asynchronously cancel these threads if the OS platform supports cancellation.  Otherwise, perform a "cooperative" cancellation.

## Collect thread handles in the thread manager. Notice that

the collected information is just a snapshot.

```
int hthread_within (ACE_hthread_t handle);
```

```
int thread_within (ACE_thread_t tid);
```

Check if the thread is managed by the thread manager.  Return true if the thread is found, false otherwise.

```
int num_tasks_in_group (int grp_id);
```

Returns the number of ACE\_Task\_Base in a group.

```
int num_threads_in_task (ACE_Task_Base *task);
```

Returns the number of threads in an ACE\_Task\_Base.

```
int task_list (int grp_id, ACE_Task_Base *task_list[], size_t n);
```

Returns in task\_list a list of up to n ACE\_Tasks in a group.  The caller must allocate the memory for task\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

```
int thread_list (
    ACE_Task_Base *task,
    ACE_thread_t thread_list[],
    size_t n
    );
```

Returns in thread\_list a list of up to n thread ids in an ACE\_Task\_Base.  The caller must allocate the memory for thread\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

```
int hthread_list (
    ACE_Task_Base *task,
    ACE_hthread_t hthread_list[],
    size_t n
    );
```

Returns in hthread\_list a list of up to n thread handles in an ACE\_Task\_Base.  The caller must allocate memory for hthread\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

```
int thread_grp_list (
    int grp_id,
    ACE_thread_t thread_list[],
    size_t n
    );
```

Returns in thread\_list a list of up to n thread ids in a group grp\_id.  The caller must allocate the memory for thread\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

```
int hthread_grp_list (
    int grp_id,
    ACE_hthread_t hthread_list[],
    size_t n
    );
```

Returns in hthread\_list a list of up to n thread handles in a group grp\_id.  The caller must allocate memory for hthread\_list.

```
int task_all_list (ACE_Task_Base *task_list[], size_t n);
```

Returns in task\_list a list of up to n ACE\_Tasks.  The caller must allocate the memory for task\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

```
int thread_all_list (ACE_thread_t thread_list[], size_t n);
```

Returns in thread\_list a list of up to n thread ids.  The caller must allocate the memory for thread\_list.  In case of an error, -1 is returned. If no requested values are found, 0 is returned, otherwise correct number of retrieved values are returned.

## Set/get group ids for a particular task.

```
int set_grp (ACE_Task_Base *task, int grp_id);
```

```
int get_grp (ACE_Task_Base *task, int &grp_id);
```

```
int count_threads (void) const;
```

Return a count of the current number of threads active in the Thread\_Manager.

```
int at_exit (ACE_At_Thread_Exit* cleanup);
```

Register an At\_Thread\_Exit hook and the ownership is acquire by Thread\_Descriptor, this is the usual case when the AT is dynamically allocated.

```
int at_exit (ACE_At_Thread_Exit& cleanup);
```

Register an At\_Thread\_Exit hook and the ownership is retained for the caller. Normally used when the at\_exit hook is created in stack.

```
int at_exit (
    void *object,
    ACE_CLEANUP_FUNC cleanup_hook,
    void *param
    );
```

\*\*\* This function is deprecated.  Please use the previous two \*\*\* at\_exit method.  Notice that you should avoid mixing this method \*\*\* with the previous two at\_exit methods.

Register an object (or array) for cleanup at thread termination.  "cleanup\_hook" points to a (global, or static member) function that is called for the object or array when it to be destroyed.  It may perform any necessary cleanup specific for that object or its class.  "param" is passed as the second parameter to the "cleanup\_hook" function; the first parameter is the object (or array) to be destroyed. "cleanup\_hook", for example, may delete the object (or array). If cleanup\_hook == 0, the object will \_NOT\_ get cleanup at thread exit.  You can use this to cancel the previously added at\_exit.

```
void wait_on_exit (int dowait);
```

```
int wait_on_exit (void);
```

Access function to determine whether the Thread\_Manager will wait for its thread to exit or not when being closing down.

```
void dump (void);
```

Dump the state of an object.

```
ACE_ALLOC_HOOK_DECLARE;
```

Declare the dynamic allocation hooks.

## The following four methods implement a simple scheme for

operating on a collection of threads atomically.

```
int check_state (
    ACE_UINT32 state,
    ACE_thread_t thread,
    int enable = 1
    );
```

Efficiently check whether thread is in a particular state. This call updates the TSS cache if possible to speed up subsequent searches.

```
int apply_task (ACE_Task_Base *task, ACE_THR_MEMBER_FUNC, int = 0);
```

Apply func to all members of the table that match the task

```
int apply_grp (int grp_id, ACE_THR_MEMBER_FUNC func, int arg = 0);
```

Apply func to all members of the table that match the grp\_id.

```
int apply_all (ACE_THR_MEMBER_FUNC, int = 0);
```

Apply func to all members of the table.

```
int join_thr (ACE_Thread_Descriptor *td, int = 0);
```

Join the thread described in tda.

```
int resume_thr (ACE_Thread_Descriptor *td, int = 0);
```

Resume the thread described in tda.

```
int suspend_thr (ACE_Thread_Descriptor *td, int = 0);
```

Suspend the thread described in tda.

```
int kill_thr (ACE_Thread_Descriptor *td, int signum);
```

Send signal signum to the thread described in tda.

```
int cancel_thr (ACE_Thread_Descriptor *td, int async_cancel = 0);
```

Set the cancellation flag for the thread described in tda.

```
int register_as_terminated (ACE_Thread_Descriptor *td);
```

Register a thread as terminated and put it into the terminated\_thr\_list\_.

```
ACE_Double_Linked_List<ACE_Thread_Descriptor> thr_list_;
```

Keeping a list of thread descriptors within the thread manager. Double-linked list enables us to cache the entries in TSS and adding/removing thread descriptor entries without affecting other thread's descriptor entries.

```
    ACE_Double_Linked_List<ACE_Thread_Descriptor_Base> terminated_thr_list_;

```

Collect terminated but not yet joined thread entries.

```
ACE_Unbounded_Queue<ACE_Thread_Descriptor*> thr_to_be_removed_;
```

Collect pointers to thread descriptors of threads to be removed later.

```
int grp_id_;
```

Keeps track of the next group id to assign.

```
int automatic_wait_;
```

Set if we want the Thread\_Manager to wait on all threads before being closed, reset otherwise.

## ACE\_Thread\_Mutex and condition variable for synchronizing termination.

```
ACE_Thread_Mutex lock_;
```

Serialize access to the zero\_cond\_.

```
ACE_Condition_Thread_Mutex zero_cond_;
```

Keep track of when there are no more threads.

## AUTHOR

Doug Schmidt

## LIBRARY

ace
