NCBI C++ ToolKit
ncbithr.hpp
Go to the documentation of this file.
00001 #ifndef CORELIB___NCBITHR__HPP
00002 #define CORELIB___NCBITHR__HPP
00003 
00004 /*  $Id: ncbithr.hpp 53291 2012-03-06 20:39:08Z lavr $
00005  * ===========================================================================
00006  *
00007  *                            PUBLIC DOMAIN NOTICE
00008  *               National Center for Biotechnology Information
00009  *
00010  *  This software/database is a "United States Government Work" under the
00011  *  terms of the United States Copyright Act.  It was written as part of
00012  *  the author's official duties as a United States Government employee and
00013  *  thus cannot be copyrighted.  This software/database is freely available
00014  *  to the public for use. The National Library of Medicine and the U.S.
00015  *  Government have not placed any restriction on its use or reproduction.
00016  *
00017  *  Although all reasonable efforts have been taken to ensure the accuracy
00018  *  and reliability of the software and data, the NLM and the U.S.
00019  *  Government do not and cannot warrant the performance or results that
00020  *  may be obtained by using this software or data. The NLM and the U.S.
00021  *  Government disclaim all warranties, express or implied, including
00022  *  warranties of performance, merchantability or fitness for any particular
00023  *  purpose.
00024  *
00025  *  Please cite the author in any work or product based on this material.
00026  *
00027  * ===========================================================================
00028  *
00029  * Author:  Denis Vakatov, Aleksey Grichenko
00030  *
00031  *
00032  */
00033 
00034 /// @file ncbithr.hpp
00035 /// Multi-threading -- classes, functions, and features.
00036 ///
00037 ///   TLS:
00038 ///   -   CTlsBase         -- TLS implementation (base class for CTls<>)
00039 ///   -   CTls<>           -- thread local storage template
00040 ///
00041 ///   THREAD:
00042 ///   -   CThread          -- thread wrapper class
00043 ///
00044 
00045 
00046 #include <corelib/ncbimtx.hpp>
00047 #include <corelib/ncbi_process.hpp>
00048 #include <corelib/ncbi_safe_static.hpp>
00049 #include <list>
00050 
00051 
00052 BEGIN_NCBI_SCOPE
00053 
00054 /** @addtogroup Threads
00055  *
00056  * @{
00057  */
00058 
00059 
00060 /////////////////////////////////////////////////////////////////////////////
00061 ///
00062 /// CTlBase --
00063 ///
00064 /// Base class for CTls<> for storing thread-specific data.
00065 
00066 class NCBI_XNCBI_EXPORT CTlsBase : public CObject
00067 {
00068     friend class CRef<CTlsBase>;
00069     friend class CUsedTlsBases;
00070     friend class CStaticTlsHelper;
00071 
00072 public:
00073     typedef void (*FCleanupBase)(void* value, void* cleanup_data);
00074 
00075 protected:
00076     /// Constructor.
00077     CTlsBase(bool auto_destroy)
00078         : m_AutoDestroy(auto_destroy)
00079     {}
00080 
00081     /// Destructor.
00082     ///
00083     /// Cleanup data and delete TLS key.
00084     ~CTlsBase(void)
00085     {
00086         if (m_AutoDestroy) {
00087             x_Destroy();
00088         }
00089     }
00090 
00091     /// Helper method to get stored thread data.
00092     void* x_GetValue(void) const;
00093 
00094     /// Helper method to set thread data.
00095     void x_SetValue(void* value, FCleanupBase cleanup=0, void* cleanup_data=0);
00096 
00097     /// Helper method to reset thread data.
00098     void x_Reset(void);
00099 
00100 protected:
00101     /// Initialize thread data
00102     void x_Init(void);
00103 
00104     /// Destroy thread data
00105     void x_Destroy(void);
00106 
00107 private:
00108     TTlsKey m_Key;              ///<
00109     bool    m_Initialized;      ///< Indicates if thread data initialized.
00110     bool    m_AutoDestroy;      ///< Indicates if object should be destroyed
00111                                 ///< in destructor
00112 
00113     /// Internal structure to store all three pointers in the same TLS.
00114     struct STlsData {
00115         void*        m_Value;
00116         FCleanupBase m_CleanupFunc;
00117         void*        m_CleanupData;
00118     };
00119 
00120     /// Helper method to get the STlsData*
00121     STlsData* x_GetTlsData(void) const;
00122     /// Deletes STlsData* structure and managed pointer
00123     /// Returns true if CTlsBase must be deregistered from current thread
00124     bool x_DeleteTlsData(void);
00125 
00126 public:
00127     static void CleanupTlsData(void *data);
00128 };
00129 
00130 
00131 
00132 /////////////////////////////////////////////////////////////////////////////
00133 ///
00134 /// CTls --
00135 ///
00136 /// Define template class for thread local storage.
00137 
00138 template <class TValue>
00139 class CTls : public CTlsBase
00140 {
00141 public:
00142     CTls(void) : CTlsBase(true)
00143     {
00144         DoDeleteThisObject();
00145         x_Init();
00146     }
00147 
00148     /// Get the pointer previously stored by SetValue().
00149     ///
00150     /// Return 0 if no value has been stored, or if Reset() was last called.
00151     /// @sa
00152     ///   SetValue()
00153     TValue* GetValue(void) const
00154     {
00155         return reinterpret_cast<TValue*> (x_GetValue());
00156     }
00157 
00158     /// Define cleanup function type, FCleanup.
00159     typedef void (*FCleanup)(TValue* value, void* cleanup_data);
00160 
00161     /// Set value.
00162     ///
00163     /// Cleanup previously stored value, and set the new value.
00164     /// The "cleanup" function and "cleanup_data" will be used to
00165     /// destroy the new "value" in the next call to SetValue() or Reset().
00166     /// Do not cleanup if the new value is equal to the old one.
00167     /// @param value
00168     ///   New value to set.
00169     /// @param cleanup
00170     ///   Cleanup function.
00171     ///   Do not cleanup if default of 0 is specified or if new value is the
00172     ///   same as old value.
00173     /// @param cleanup_data
00174     ///   One of the parameters to the cleanup function.
00175     /// @sa
00176     ///   GetValue()
00177     void SetValue(TValue* value, FCleanup cleanup = 0, void* cleanup_data = 0)
00178     {
00179         x_SetValue(value,
00180                    reinterpret_cast<FCleanupBase> (cleanup), cleanup_data);
00181     }
00182 
00183     /// Reset thread local storage.
00184     ///
00185     /// Reset thread local storage to its initial value (as it was before the
00186     /// first call to SetValue()). Do cleanup if the cleanup function was
00187     /// specified in the previous call to SetValue().
00188     ///
00189     /// Reset() will always be called automatically on the thread termination,
00190     /// or when the TLS is destroyed.
00191     void Reset(void) { x_Reset(); }
00192 
00193     /// Discard thread local storage.
00194     ///
00195     /// Schedule the TLS to be destroyed as soon as there are no CRef to it
00196     /// left.
00197     void Discard(void) { x_Reset(); }
00198 };
00199 
00200 
00201 #define NCBI_STATIC_TLS_VIA_SAFE_STATIC_REF 1
00202 
00203 #if NCBI_STATIC_TLS_VIA_SAFE_STATIC_REF
00204 template<class TValue>
00205 class CStaticTls : private CSafeStaticRef< CTls<TValue> >
00206 {
00207 private:
00208     typedef CSafeStaticRef< CTls<TValue> > TParent;
00209 
00210 public:
00211     typedef CSafeStaticLifeSpan TLifeSpan;
00212     /// User cleanup function type
00213     typedef void (*FUserCleanup)(void*  ptr);
00214     /// Define cleanup function type, FCleanup.
00215     typedef void (*FCleanup)(TValue* value, void* cleanup_data);
00216 
00217     CStaticTls(FUserCleanup user_cleanup = 0,
00218                TLifeSpan life_span = TLifeSpan::GetDefault())
00219         : TParent(user_cleanup, life_span)
00220     {
00221     }
00222 
00223     TValue* GetValue(void) {
00224         return TParent::Get().GetValue();
00225     }
00226     void SetValue(TValue* value, FCleanup cleanup = 0, void* cleanup_data = 0){
00227         TParent::Get().SetValue(value, cleanup, cleanup_data);
00228     }
00229 
00230     friend class CUsedTlsBases;
00231 };
00232 
00233 #else // !NCBI_STATIC_TLS_VIA_SAFE_STATIC_REF
00234 template <class TValue> class CStaticTls;
00235 
00236 /// Helper class to control life time of CStaticTls object
00237 class CStaticTlsHelper : public CSafeStaticPtr_Base
00238 {
00239 private:
00240     template <class TValue> friend class CStaticTls;
00241 
00242     CStaticTlsHelper(FUserCleanup user_cleanup,
00243                      TLifeSpan    life_span)
00244         : CSafeStaticPtr_Base(SelfCleanup, user_cleanup, life_span)
00245     {}
00246 
00247     static void SelfCleanup(void** ptr)
00248     {
00249         CTlsBase* tmp = static_cast<CTlsBase*>(*ptr);
00250         if (tmp) {
00251             tmp->x_Destroy();
00252             *ptr = NULL;
00253         }
00254     }
00255 };
00256 
00257 
00258 /////////////////////////////////////////////////////////////////////////////
00259 ///
00260 /// CStaticTls --
00261 ///
00262 /// Define template class for thread local storage in static variable
00263 /// (as thread local storage objects are meaningful only in static content).
00264 /// Class can be used only as static variable type.
00265 
00266 template <class TValue>
00267 class CStaticTls : public CTlsBase
00268 {
00269 public:
00270     /// Life span
00271     typedef CSafeStaticLifeSpan TLifeSpan;
00272     /// User cleanup function type
00273     typedef void (*FUserCleanup)(void*  ptr);
00274 
00275     // Set user-provided cleanup function to be executed on destruction.
00276     // Life span allows to control destruction of objects. Objects with
00277     // the same life span are destroyed in the order reverse to their
00278     // creation order.
00279     CStaticTls(FUserCleanup user_cleanup = 0,
00280                TLifeSpan life_span = TLifeSpan::GetDefault())
00281         : CTlsBase(false),
00282           m_SafeHelper(user_cleanup, life_span)
00283     {}
00284 
00285     /// Get the pointer previously stored by SetValue().
00286     ///
00287     /// Return 0 if no value has been stored, or if Reset() was last called.
00288     /// @sa
00289     ///   SetValue()
00290     TValue* GetValue(void)
00291     {
00292         if (!m_SafeHelper.m_Ptr) {
00293             x_SafeInit();
00294         }
00295         return reinterpret_cast<TValue*> (x_GetValue());
00296     }
00297 
00298     /// Define cleanup function type, FCleanup.
00299     typedef void (*FCleanup)(TValue* value, void* cleanup_data);
00300 
00301     /// Set value.
00302     ///
00303     /// Cleanup previously stored value, and set the new value.
00304     /// The "cleanup" function and "cleanup_data" will be used to
00305     /// destroy the new "value" in the next call to SetValue() or Reset().
00306     /// Do not cleanup if the new value is equal to the old one.
00307     /// @param value
00308     ///   New value to set.
00309     /// @param cleanup
00310     ///   Cleanup function.
00311     ///   Do not cleanup if default of 0 is specified or if new value is the
00312     ///   same as old value.
00313     /// @param cleanup_data
00314     ///   One of the parameters to the cleanup function.
00315     /// @sa
00316     ///   GetValue()
00317     void SetValue(TValue* value, FCleanup cleanup = 0, void* cleanup_data = 0)
00318     {
00319         if (!m_SafeHelper.m_Ptr) {
00320             x_SafeInit();
00321         }
00322         x_SetValue(value,
00323                    reinterpret_cast<FCleanupBase> (cleanup), cleanup_data);
00324     }
00325 
00326     /// Reset thread local storage.
00327     ///
00328     /// Reset thread local storage to its initial value (as it was before the
00329     /// first call to SetValue()). Do cleanup if the cleanup function was
00330     /// specified in the previous call to SetValue().
00331     ///
00332     /// Reset() will always be called automatically on the thread termination,
00333     /// or when the TLS is destroyed.
00334     void Reset(void)
00335     {
00336         if (!m_SafeHelper.m_Ptr) {
00337             x_SafeInit();
00338         }
00339         x_Reset();
00340     }
00341 
00342 private:
00343     /// Object derived from CSafeStaticPtr_Base to help manage life time
00344     /// of the object
00345     CStaticTlsHelper m_SafeHelper;
00346 
00347     /// Initialize the object in SafeStaticRef-ish manner
00348     void x_SafeInit(void);
00349 };
00350 #endif // NCBI_STATIC_TLS_VIA_SAFE_STATIC_REF
00351 
00352 class NCBI_XNCBI_EXPORT CUsedTlsBases
00353 {
00354 public:
00355     CUsedTlsBases(void);
00356     ~CUsedTlsBases(void);
00357 
00358     /// The function must be called before thread termination when
00359     /// using native threads instead of CThread. Otherwise any data
00360     /// allocated by the thread and put into TLS will not be destroyed
00361     /// and will cause memory leaks.
00362     void ClearAll(void);
00363 
00364     void Register(CTlsBase* tls);
00365     void Deregister(CTlsBase* tls);
00366 
00367     /// Get the list of used TLS-es for the current thread
00368     static CUsedTlsBases& GetUsedTlsBases(void);
00369 
00370 private:
00371     typedef set<CTlsBase*> TTlsSet;
00372     TTlsSet m_UsedTls;
00373 
00374     static CStaticTls<CUsedTlsBases> sm_UsedTlsBases;
00375 
00376 private:
00377     CUsedTlsBases(const CUsedTlsBases&);
00378     void operator=(const CUsedTlsBases&);
00379 };
00380 
00381 
00382 /////////////////////////////////////////////////////////////////////////////
00383 ///
00384 /// CThread --
00385 ///
00386 /// Thread wrapper class.
00387 ///
00388 ///  Base class for user-defined threads. Creates the new thread, then
00389 ///  calls user-provided Main() function. The thread then can be detached
00390 ///  or joined. In any case, explicit destruction of the thread is prohibited.
00391 
00392 class NCBI_XNCBI_EXPORT CThread : public CObject
00393 {
00394     friend class CRef<CThread>;
00395     friend class CTlsBase;
00396 
00397 public:
00398     /// Constructor.
00399     ///
00400     /// Must be allocated in the heap only!.
00401     CThread(void);
00402 
00403     /// Which mode should the thread run in.
00404     enum ERunMode {
00405         fRunDefault  = 0x00,    ///< Default mode
00406         fRunDetached = 0x01,    ///< Run the thread detached (non-joinable)
00407         fRunBound    = 0x10,    ///< Run thread in a 1:1 thread:LPW mode
00408                                 ///< - may not be supported and will be
00409                                 ///< ignored on some platforms
00410         fRunUnbound  = 0x20,    ///< Run thread in a N:1 thread:LPW mode
00411                                 ///< - may not be supported and will be
00412                                 ///< ignored on some platforms
00413         fRunNice     = 0x40,    ///< Run thread with low priority (MS-Win only)
00414         fRunAllowST  = 0x100    ///< Allow threads to run in single thread
00415                                 ///< builds
00416     };
00417 
00418     /// Bitwise OR'd flags for thread creation passed to Run().
00419     typedef int TRunMode;
00420 
00421     /// Run the thread.
00422     ///
00423     /// Create a new thread, initialize it, and call user-provided Main()
00424     /// method.
00425     bool Run(TRunMode flags = fRunDefault);
00426 
00427     /// Inform the thread that user does not need to wait for its termination.
00428     /// The thread object will be destroyed by Exit().
00429     /// If the thread has already been terminated by Exit, Detach() will
00430     /// also schedule the thread object for destruction.
00431     /// NOTE:  it is no more safe to use this thread object after Detach(),
00432     ///        unless there are still CRef<> based references to it!
00433     void Detach(void);
00434 
00435     /// Wait for the thread termination.
00436     /// The thread object will be scheduled for destruction right here,
00437     /// inside Join(). Only one call to Join() is allowed.
00438     void Join(void** exit_data = 0);
00439 
00440     /// Cancel current thread. If the thread is detached, then schedule
00441     /// the thread object for destruction.
00442     /// Cancellation is performed by throwing an exception of type
00443     /// CExitThreadException to allow destruction of all objects in
00444     /// thread's stack, so Exit() method shell not be called from any
00445     /// destructor.
00446     static void Exit(void* exit_data);
00447 
00448     /// If the thread has not been Run() yet, then schedule the thread object
00449     /// for destruction, and return TRUE.
00450     /// Otherwise, do nothing, and return FALSE.
00451     bool Discard(void);
00452 
00453     /// Get ID of current thread (for main thread it is always zero).
00454     typedef unsigned int TID;
00455     static TID GetSelf(void);
00456 
00457     /// Get current CThread object (or NULL, if main thread)
00458     static CThread* GetCurrentThread(void);
00459 
00460     /// Get system ID of the current thread - for internal use only.
00461     /// The ID is unique only while the thread is running and may be
00462     /// re-used by another thread later.
00463     static void GetSystemID(TThreadSystemID* id);
00464 
00465     /// Get total amount of threads
00466     /// This amount does not contain main thread.
00467     static unsigned int GetThreadsCount();
00468 
00469     /// Initialize main thread's TID.
00470     /// The function must be called from the main thread if the application
00471     /// is using non-toolkit threads. Otherwise getting thread id of a
00472     /// native thread will return zero.
00473     static void InitializeMainThreadId(void);
00474 
00475 protected:
00476     /// Derived (user-created) class must provide a real thread function.
00477     virtual void* Main(void) = 0;
00478 
00479     /// Override this to execute finalization code.
00480     /// Unlike destructor, this code will be executed before
00481     /// thread termination and as a part of the thread.
00482     virtual void OnExit(void);
00483 
00484     /// To be called only internally!
00485     /// NOTE:  destructor of the derived (user-provided) class should be
00486     ///        declared "protected", too!
00487     virtual ~CThread(void);
00488 
00489     TThreadHandle GetThreadHandle();
00490 
00491 private:
00492     TThreadHandle m_Handle;        ///< platform-dependent thread handle
00493     bool          m_IsRun;         ///< if Run() was called for the thread
00494     bool          m_IsDetached;    ///< if the thread is detached
00495     bool          m_IsJoined;      ///< if Join() was called for the thread
00496     bool          m_IsTerminated;  ///< if Exit() was called for the thread
00497     CRef<CThread> m_SelfRef;       ///< "this" -- to avoid premature destruction
00498     void*         m_ExitData;      ///< as returned by Main() or passed to Exit()
00499 
00500 #if defined NCBI_THREAD_PID_WORKAROUND
00501     friend class CProcess;
00502     TPid          m_ThreadPID;     ///< Cache thread PID to detect forks
00503 
00504     static TPid sx_GetThreadPid(void);
00505     static void sx_SetThreadPid(TPid pid);
00506 #endif
00507 
00508     static unsigned int sm_ThreadsCount;  ///< Total amount of threads
00509 
00510     /// Function to use (internally) as the thread's startup function
00511     static TWrapperRes Wrapper(TWrapperArg arg);
00512     friend TWrapperRes ThreadWrapperCaller(TWrapperArg arg);
00513 
00514     struct SThreadInfo {
00515         CThread* thread_ptr;
00516         TID      thread_id;
00517     };
00518 
00519     /// To store "CThread" object related to the current (running) thread
00520     static CStaticTls<SThreadInfo>* sm_ThreadsTls;
00521     static bool sm_MainThreadIdInitialized;
00522 
00523     /// Safe access to "sm_ThreadsTls"
00524     static CStaticTls<SThreadInfo>& GetThreadsTls(void)
00525     {
00526         if ( !sm_ThreadsTls ) {
00527             CreateThreadsTls();
00528         }
00529         return *sm_ThreadsTls;
00530     }
00531 
00532     static void sx_CleanupThreadInfo(SThreadInfo* info, void* cleanup_data);
00533     static SThreadInfo* sx_InitThreadInfo(CThread* thread_obj);
00534     static int sx_GetNextThreadId(void);
00535 
00536     /// sm_ThreadsTls initialization and cleanup functions
00537     static void CreateThreadsTls(void);
00538     friend void s_CleanupThreadsTls(void* /* ptr */);
00539 
00540     /// Prohibit copying and assigning
00541     CThread(const CThread&);
00542     CThread& operator= (const CThread&);
00543 };
00544 
00545 
00546 class NCBI_XNCBI_EXPORT CThreadException : EXCEPTION_VIRTUAL_BASE public CException
00547 {
00548 public:
00549     enum EErrCode {
00550         eRunError,          ///< Failed to run thread
00551         eControlError,      ///< Failed to control thread's state
00552         eOther              ///< Other thread errors
00553     };
00554 
00555     /// Translate from the error code value to its string representation.
00556     virtual const char* GetErrCodeString(void) const;
00557 
00558     // Standard exception boilerplate code.
00559     NCBI_EXCEPTION_DEFAULT(CThreadException, CException);
00560 };
00561 
00562 
00563 /* @} */
00564 
00565 
00566 /////////////////////////////////////////////////////////////////////////////
00567 
00568 /////////////////////////////////////////////////////////////////////////////
00569 //  IMPLEMENTATION of INLINE functions
00570 /////////////////////////////////////////////////////////////////////////////
00571 
00572 
00573 
00574 /////////////////////////////////////////////////////////////////////////////
00575 //  CTlsBase::
00576 //
00577 
00578 inline
00579 CTlsBase::STlsData* CTlsBase::x_GetTlsData(void)
00580 const
00581 {
00582     if ( !m_Initialized ) {
00583         return 0;
00584     }
00585 
00586     void* tls_data;
00587 
00588 #if defined(NCBI_WIN32_THREADS)
00589     tls_data = TlsGetValue(m_Key);
00590 #elif defined(NCBI_POSIX_THREADS)
00591     tls_data = pthread_getspecific(m_Key);
00592 #else
00593     tls_data = m_Key;
00594 #endif
00595 
00596     return static_cast<STlsData*> (tls_data);
00597 }
00598 
00599 
00600 inline
00601 void* CTlsBase::x_GetValue(void)
00602 const
00603 {
00604     // Get TLS-stored structure
00605     STlsData* tls_data = x_GetTlsData();
00606 
00607     // If assigned, extract and return user data
00608     return tls_data ? tls_data->m_Value : 0;
00609 }
00610 
00611 
00612 
00613 /////////////////////////////////////////////////////////////////////////////
00614 //  CThread::
00615 //
00616 
00617 #if !NCBI_STATIC_TLS_VIA_SAFE_STATIC_REF
00618 template <class TValue>
00619 inline
00620 void CStaticTls<TValue>::x_SafeInit(void)
00621 {
00622     bool mutex_locked = false;
00623     if ( m_SafeHelper.Init_Lock(&mutex_locked) ) {
00624         // Init the object and register for cleanup
00625         try {
00626             x_Init();
00627             m_SafeHelper.m_Ptr = this;
00628             CSafeStaticGuard::Register(&m_SafeHelper);
00629         }
00630         catch (CException& e) {
00631             m_SafeHelper.Init_Unlock(mutex_locked);
00632             NCBI_RETHROW_SAME(e,
00633                               "CStaticTls::x_CheckInit: Register() failed");
00634         }
00635         catch (...) {
00636             m_SafeHelper.Init_Unlock(mutex_locked);
00637             NCBI_THROW(CCoreException, eCore,
00638                        "CStaticTls::x_CheckInit: Register() failed");
00639         }
00640     }
00641     m_SafeHelper.Init_Unlock(mutex_locked);
00642 }
00643 #endif
00644 
00645 
00646 /////////////////////////////////////////////////////////////////////////////
00647 //  CThread::
00648 //
00649 
00650 inline
00651 CThread::TID CThread::GetSelf(void)
00652 {
00653     SThreadInfo* info = GetThreadsTls().GetValue();
00654     if (!info  &&  sm_MainThreadIdInitialized) {
00655         // Info has not been set - this is a native thread,
00656         // need to assign an ID.
00657         info = sx_InitThreadInfo(0);
00658     }
00659     // If zero, it is main thread which has no CThread object
00660     return info ? info->thread_id : 0;
00661 }
00662 
00663 
00664 inline
00665 CThread* CThread::GetCurrentThread(void)
00666 {
00667     // Get pointer to the current thread object
00668     SThreadInfo* info = GetThreadsTls().GetValue();
00669     return info ? info->thread_ptr : 0;
00670 }
00671 
00672 
00673 inline
00674 TThreadHandle CThread::GetThreadHandle()
00675 {
00676     return m_Handle;
00677 }
00678 
00679 
00680 inline
00681 unsigned int CThread::GetThreadsCount() {
00682     return sm_ThreadsCount;
00683 }
00684 
00685 
00686 // Special value, stands for "no thread" thread ID
00687 const CThread::TID kThreadID_None = 0xFFFFFFFF;
00688 
00689 
00690 END_NCBI_SCOPE
00691 
00692 #endif  /* NCBITHR__HPP */
Modified on Wed May 23 13:17:54 2012 by modify_doxy.py rev. 337098