X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8d31c13865c0254b2f4c784677f8537b1e81593f..789dbcd4e35cdab7085c6f15c2cb10615cdb3624:/src/gtk/threadpsx.cpp diff --git a/src/gtk/threadpsx.cpp b/src/gtk/threadpsx.cpp index f6ffab19b9..99239908ef 100644 --- a/src/gtk/threadpsx.cpp +++ b/src/gtk/threadpsx.cpp @@ -5,317 +5,754 @@ // Modified by: // Created: 04/22/98 // RCS-ID: $Id$ -// Copyright: (c) Wolfram Gloger (1996, 1997); Guilhem Lavaux (1998) +// Copyright: (c) Wolfram Gloger (1996, 1997) +// Guilhem Lavaux (1998) +// Robert Roebling (1999) // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// + #ifdef __GNUG__ -#pragma implementation "thread.h" + #pragma implementation "thread.h" #endif #include #include -#include #include +#include -enum thread_state { - STATE_IDLE = 0, - STATE_RUNNING, - STATE_CANCELED, - STATE_EXITED -}; +#ifdef __linux__ + #include +#endif + +#ifdef __SUN__ +extern int usleep(unsigned int useconds); +#endif -///////////////////////////////////////////////////////////////////////////// -// Static variables -///////////////////////////////////////////////////////////////////////////// #include "wx/thread.h" +#include "wx/module.h" +#include "wx/utils.h" +#include "wx/log.h" +#include "wx/intl.h" +#include "wx/dynarray.h" -static pthread_t p_mainid; -wxMutex wxMainMutex; // controls access to all GUI functions +#include "gdk/gdk.h" +#include "gtk/gtk.h" + +enum thread_state +{ + STATE_NEW, // didn't start execution yet (=> RUNNING) + STATE_RUNNING, + STATE_PAUSED, + STATE_CANCELED, + STATE_EXITED +}; + +WX_DEFINE_ARRAY(wxThread *, wxArrayThread); + +// ----------------------------------------------------------------------------- +// global data +// ----------------------------------------------------------------------------- + +// we keep the list of all threads created by the application to be able to +// terminate them on exit if there are some left - otherwise the process would +// be left in memory +static wxArrayThread gs_allThreads; + +// the id of the main thread +static pthread_t gs_tidMain; + +// the key for the pointer to the associated wxThread object +static pthread_key_t gs_keySelf; + +// this mutex must be acquired before any call to a GUI function +static wxMutex *gs_mutexGui; + +//-------------------------------------------------------------------- +// common GUI thread code +//-------------------------------------------------------------------- -///////////////////////////////////////////////////////////////////////////// -// GUI thread manager -///////////////////////////////////////////////////////////////////////////// #include "threadgui.inc" -///////////////////////////////////////////////////////////////////////////// -// wxThread: Posix Thread implementation (Mutex) -///////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------- +// wxMutex (Posix implementation) +//-------------------------------------------------------------------- -class wxMutexInternal { +class wxMutexInternal +{ public: pthread_mutex_t p_mutex; }; wxMutex::wxMutex() { - p_internal = new wxMutexInternal; - pthread_mutex_init(&(p_internal->p_mutex), NULL); - m_locked = 0; + p_internal = new wxMutexInternal; + pthread_mutex_init( &(p_internal->p_mutex), (const pthread_mutexattr_t*) NULL ); + m_locked = 0; } wxMutex::~wxMutex() { - if (m_locked > 0) - wxDebugMsg("wxMutex warning: freeing a locked mutex (%d locks)\n", - m_locked); + if (m_locked > 0) + wxLogDebug("Freeing a locked mutex (%d locks)", m_locked); - pthread_mutex_destroy(&(p_internal->p_mutex)); - delete p_internal; + pthread_mutex_destroy( &(p_internal->p_mutex) ); + delete p_internal; } wxMutexError wxMutex::Lock() { - int err; + int err = pthread_mutex_lock( &(p_internal->p_mutex) ); + if (err == EDEADLK) + { + wxLogDebug("Locking this mutex would lead to deadlock!"); + + return wxMUTEX_DEAD_LOCK; + } - err = pthread_mutex_lock(&(p_internal->p_mutex)); - if (err == EDEADLK) - return MUTEX_DEAD_LOCK; - m_locked++; - return MUTEX_NO_ERROR; + m_locked++; + + return wxMUTEX_NO_ERROR; } wxMutexError wxMutex::TryLock() { - int err; + if (m_locked) + { + return wxMUTEX_BUSY; + } + + int err = pthread_mutex_trylock( &(p_internal->p_mutex) ); + switch (err) + { + case EBUSY: return wxMUTEX_BUSY; + } - if (m_locked) - return MUTEX_BUSY; - err = pthread_mutex_trylock(&(p_internal->p_mutex)); - switch (err) { - case EBUSY: return MUTEX_BUSY; - } - m_locked++; - return MUTEX_NO_ERROR; + m_locked++; + + return wxMUTEX_NO_ERROR; } wxMutexError wxMutex::Unlock() { - if (m_locked > 0) - m_locked--; - else - return MUTEX_UNLOCKED; - pthread_mutex_unlock(&(p_internal->p_mutex)); - return MUTEX_NO_ERROR; + if (m_locked > 0) + { + m_locked--; + } + else + { + wxLogDebug("Unlocking not locked mutex."); + + return wxMUTEX_UNLOCKED; + } + + pthread_mutex_unlock( &(p_internal->p_mutex) ); + + return wxMUTEX_NO_ERROR; } -///////////////////////////////////////////////////////////////////////////// -// wxThread: Posix Thread implementation (Condition) -///////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------- +// wxCondition (Posix implementation) +//-------------------------------------------------------------------- -class wxConditionInternal { +class wxConditionInternal +{ public: pthread_cond_t p_condition; }; wxCondition::wxCondition() { - p_internal = new wxConditionInternal; - pthread_cond_init(&(p_internal->p_condition), NULL); + p_internal = new wxConditionInternal; + pthread_cond_init( &(p_internal->p_condition), (const pthread_condattr_t *) NULL ); } wxCondition::~wxCondition() { - pthread_cond_destroy(&(p_internal->p_condition)); - delete p_internal; + pthread_cond_destroy( &(p_internal->p_condition) ); + + delete p_internal; } void wxCondition::Wait(wxMutex& mutex) { - pthread_cond_wait(&(p_internal->p_condition), &(mutex.p_internal->p_mutex)); + pthread_cond_wait( &(p_internal->p_condition), &(mutex.p_internal->p_mutex) ); } bool wxCondition::Wait(wxMutex& mutex, unsigned long sec, unsigned long nsec) { - struct timespec tspec; + struct timespec tspec; - tspec.tv_sec = time(NULL)+sec; - tspec.tv_nsec = nsec; - return (pthread_cond_timedwait(&(p_internal->p_condition), &(mutex.p_internal->p_mutex), &tspec) != ETIMEDOUT); + tspec.tv_sec = time(0L)+sec; + tspec.tv_nsec = nsec; + return (pthread_cond_timedwait(&(p_internal->p_condition), &(mutex.p_internal->p_mutex), &tspec) != ETIMEDOUT); } void wxCondition::Signal() { - pthread_cond_signal(&(p_internal->p_condition)); + pthread_cond_signal( &(p_internal->p_condition) ); } void wxCondition::Broadcast() { - pthread_cond_broadcast(&(p_internal->p_condition)); + pthread_cond_broadcast( &(p_internal->p_condition) ); } -///////////////////////////////////////////////////////////////////////////// -// wxThread: Posix Thread implementation (Thread) -///////////////////////////////////////////////////////////////////////////// +//-------------------------------------------------------------------- +// wxThread (Posix implementation) +//-------------------------------------------------------------------- -class wxThreadInternal { +class wxThreadInternal +{ public: - wxThreadInternal() { state = STATE_IDLE; } - ~wxThreadInternal() {} - static void *PthreadStart(void *ptr); - pthread_t thread_id; - int state; - int prio; + wxThreadInternal(); + ~wxThreadInternal(); + + // thread entry function + static void *PthreadStart(void *ptr); + + // thread actions + // start the thread + wxThreadError Run(); + // ask the thread to terminate + void Cancel(); + // wake up threads waiting for our termination + void SignalExit(); + // go to sleep until Resume() is called + void Pause(); + // resume the thread + void Resume(); + + // accessors + // priority + int GetPriority() const { return m_prio; } + void SetPriority(int prio) { m_prio = prio; } + // state + thread_state GetState() const { return m_state; } + void SetState(thread_state state) { m_state = state; } + // id + pthread_t GetId() const { return thread_id; } + // "cancelled" flag + bool WasCancelled() const { return m_cancelled; } + +//private: -- should be! + pthread_t thread_id; + +private: + thread_state m_state; // see thread_state enum + int m_prio; // in wxWindows units: from 0 to 100 + + // set when the thread should terminate + bool m_cancelled; + + // this (mutex, cond) pair is used to synchronize the main thread and this + // thread in several situations: + // 1. The thread function blocks until condition is signaled by Run() when + // it's initially created - this allows create thread in "suspended" + // state + // 2. The Delete() function blocks until the condition is signaled when the + // thread exits. + wxMutex m_mutex; + wxCondition m_cond; + + // another (mutex, cond) pair for Pause()/Resume() usage + // + // VZ: it's possible that we might reuse the mutex and condition from above + // for this too, but as I'm not at all sure that it won't create subtle + // problems with race conditions between, say, Pause() and Delete() I + // prefer this may be a bit less efficient but much safer solution + wxMutex m_mutexSuspend; + wxCondition m_condSuspend; }; void *wxThreadInternal::PthreadStart(void *ptr) { - wxThread *thread = (wxThread *)ptr; + wxThread *thread = (wxThread *)ptr; + wxThreadInternal *pthread = thread->p_internal; - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); - void* status = thread->Entry(); - thread->Exit(status); + if ( pthread_setspecific(gs_keySelf, thread) != 0 ) + { + wxLogError(_("Can not start thread: error writing TLS.")); - return NULL; + return (void *)-1; + } + + // wait for the condition to be signaled from Run() + // mutex state: currently locked by the thread which created us + pthread->m_cond.Wait(pthread->m_mutex); + + // mutex state: locked again on exit of Wait() + + // call the main entry + void* status = thread->Entry(); + + // terminate the thread + thread->Exit(status); + + wxFAIL_MSG("wxThread::Exit() can't return."); + + return NULL; } -wxThreadError wxThread::Create() +wxThreadInternal::wxThreadInternal() +{ + m_state = STATE_NEW; + m_cancelled = FALSE; + + // this mutex is locked during almost all thread lifetime - it will only be + // unlocked in the very end + m_mutex.Lock(); + + // this mutex is used in Pause()/Resume() and is also locked all the time + // unless the thread is paused + m_mutexSuspend.Lock(); +} + +wxThreadInternal::~wxThreadInternal() { - pthread_attr_t a; - int min_prio, max_prio, p; - struct sched_param sp; + m_mutexSuspend.Unlock(); - if (p_internal->state != STATE_IDLE) - return THREAD_RUNNING; + // note that m_mutex will be unlocked by the thread which waits for our + // termination +} - // Change thread priority - pthread_attr_init(&a); - pthread_attr_getschedpolicy(&a, &p); +wxThreadError wxThreadInternal::Run() +{ + wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, + "thread may only be started once after successful Create()" ); - min_prio = sched_get_priority_min(p); - max_prio = sched_get_priority_max(p); + // the mutex was locked on Create(), so we will be able to lock it again + // only when the thread really starts executing and enters the wait - + // otherwise we might signal the condition before anybody is waiting for it + wxMutexLocker lock(m_mutex); + m_cond.Signal(); - pthread_attr_getschedparam(&a, &sp); - sp.sched_priority = min_prio + - (p_internal->prio*(max_prio-min_prio))/100; - pthread_attr_setschedparam(&a, &sp); + m_state = STATE_RUNNING; - // this is the point of no return - p_internal->state = STATE_RUNNING; - if (pthread_create(&p_internal->thread_id, &a, - wxThreadInternal::PthreadStart, (void *)this) != 0) { - p_internal->state = STATE_IDLE; - pthread_attr_destroy(&a); - return THREAD_NO_RESOURCE; - } - pthread_attr_destroy(&a); - return THREAD_NO_ERROR; + return wxTHREAD_NO_ERROR; + + // now the mutex is unlocked back - but just to allow Wait() function to + // terminate by relocking it, so the net result is that the worker thread + // starts executing and the mutex is still locked +} + +void wxThreadInternal::Cancel() +{ + // if the thread we're waiting for is waiting for the GUI mutex, we will + // deadlock so make sure we release it temporarily + if ( wxThread::IsMain() ) + wxMutexGuiLeave(); + + // nobody ever writes this variable so it's safe to not use any + // synchronization here + m_cancelled = TRUE; + + // entering Wait() releases the mutex thus allowing SignalExit() to acquire + // it and to signal us its termination + m_cond.Wait(m_mutex); + + // mutex is still in the locked state - relocked on exit from Wait(), so + // unlock it - we don't need it any more, the thread has already terminated + m_mutex.Unlock(); + + // reacquire GUI mutex + if ( wxThread::IsMain() ) + wxMutexGuiEnter(); } -void wxThread::SetPriority(int prio) +void wxThreadInternal::SignalExit() { - if (p_internal->state == STATE_RUNNING) - return; + // as mutex is currently locked, this will block until some other thread + // (normally the same which created this one) unlocks it by entering Wait() + m_mutex.Lock(); - if (prio > 100) - prio = 100; - if (prio < 0) - prio = 0; - p_internal->prio = prio; + // wake up all the threads waiting for our termination + m_cond.Broadcast(); + + // after this call mutex will be finally unlocked + m_mutex.Unlock(); } -int wxThread::GetPriority() const +void wxThreadInternal::Pause() { - return p_internal->prio; + wxCHECK_RET( m_state == STATE_PAUSED, + "thread must first be paused with wxThread::Pause()." ); + + // wait until the condition is signaled from Resume() + m_condSuspend.Wait(m_mutexSuspend); } -void wxThread::DeferDestroy(bool on) +void wxThreadInternal::Resume() { - if (on) - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); - else - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + wxCHECK_RET( m_state == STATE_PAUSED, + "can't resume thread which is not suspended." ); + + // we will be able to lock this mutex only when Pause() starts waiting + wxMutexLocker lock(m_mutexSuspend); + m_condSuspend.Signal(); + + SetState(STATE_RUNNING); } -wxThreadError wxThread::Destroy() +// ----------------------------------------------------------------------------- +// static functions +// ----------------------------------------------------------------------------- + +wxThread *wxThread::This() { - int res = 0; + return (wxThread *)pthread_getspecific(gs_keySelf); +} - if (p_internal->state == STATE_RUNNING) { - res = pthread_cancel(p_internal->thread_id); - if (res == 0) - p_internal->state = STATE_CANCELED; - } - return THREAD_NO_ERROR; +bool wxThread::IsMain() +{ + return (bool)pthread_equal(pthread_self(), gs_tidMain); } -void *wxThread::Join() +void wxThread::Yield() { - void* status = 0; +#ifdef HAVE_SCHED_YIELD + sched_yield(); +#else // !HAVE_SCHED_YIELD + // may be it will have the desired effect? + Sleep(0); +#endif // HAVE_SCHED_YIELD +} - if (p_internal->state != STATE_IDLE) { - bool do_unlock = wxThread::IsMain(); +void wxThread::Sleep(unsigned long milliseconds) +{ + wxUsleep(milliseconds); +} - while (p_internal->state == STATE_RUNNING) - wxYield(); +// ----------------------------------------------------------------------------- +// creating thread +// ----------------------------------------------------------------------------- + +wxThread::wxThread() +{ + // add this thread to the global list of all threads + gs_allThreads.Add(this); - if (do_unlock) - wxMainMutex.Unlock(); - pthread_join(p_internal->thread_id, &status); - if (do_unlock) - wxMainMutex.Lock(); - p_internal->state = STATE_IDLE; - } - return status; + p_internal = new wxThreadInternal(); +} + +wxThreadError wxThread::Create() +{ + if (p_internal->GetState() != STATE_NEW) + return wxTHREAD_RUNNING; + + // set up the thread attribute: right now, we only set thread priority + pthread_attr_t attr; + pthread_attr_init(&attr); + +#ifdef HAVE_THREAD_PRIORITY_FUNCTIONS + int prio; + if ( pthread_attr_getschedpolicy(&attr, &prio) != 0 ) + { + wxLogError(_("Can not retrieve thread scheduling policy.")); + } + + int min_prio = sched_get_priority_min(prio), + max_prio = sched_get_priority_max(prio); + + if ( min_prio == -1 || max_prio == -1 ) + { + wxLogError(_("Can not get priority range for scheduling policy %d."), + prio); + } + else + { + struct sched_param sp; + pthread_attr_getschedparam(&attr, &sp); + sp.sched_priority = min_prio + + (p_internal->GetPriority()*(max_prio-min_prio))/100; + pthread_attr_setschedparam(&attr, &sp); + } +#endif // HAVE_THREAD_PRIORITY_FUNCTIONS + + // create the new OS thread object + int rc = pthread_create(&p_internal->thread_id, &attr, + wxThreadInternal::PthreadStart, (void *)this); + pthread_attr_destroy(&attr); + + if ( rc != 0 ) + { + p_internal->SetState(STATE_EXITED); + return wxTHREAD_NO_RESOURCE; + } + + return wxTHREAD_NO_ERROR; +} + +wxThreadError wxThread::Run() +{ + return p_internal->Run(); +} + +// ----------------------------------------------------------------------------- +// misc accessors +// ----------------------------------------------------------------------------- + +void wxThread::SetPriority(unsigned int prio) +{ + wxCHECK_RET( ((int)WXTHREAD_MIN_PRIORITY <= (int)prio) && + ((int)prio <= (int)WXTHREAD_MAX_PRIORITY), + "invalid thread priority" ); + + wxCriticalSectionLocker lock(m_critsect); + + switch ( p_internal->GetState() ) + { + case STATE_NEW: + // thread not yet started, priority will be set when it is + p_internal->SetPriority(prio); + break; + + case STATE_RUNNING: + case STATE_PAUSED: +#ifdef HAVE_THREAD_PRIORITY_FUNCTIONS + { + struct sched_param sparam; + sparam.sched_priority = prio; + + if ( pthread_setschedparam(p_internal->GetId(), + SCHED_OTHER, &sparam) != 0 ) + { + wxLogError(_("Failed to set thread priority %d."), prio); + } + } +#endif // HAVE_THREAD_PRIORITY_FUNCTIONS + break; + + case STATE_EXITED: + default: + wxFAIL_MSG("impossible to set thread priority in this state"); + } +} + +unsigned int wxThread::GetPriority() const +{ + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); + + return p_internal->GetPriority(); } unsigned long wxThread::GetID() const { - return (unsigned long)p_internal->thread_id; + return (unsigned long)p_internal->thread_id; } -void wxThread::Exit(void *status) +// ----------------------------------------------------------------------------- +// pause/resume +// ----------------------------------------------------------------------------- + +wxThreadError wxThread::Pause() { - wxThread* ptr = this; + wxCriticalSectionLocker lock(m_critsect); - THREAD_SEND_EXIT_MSG(ptr); - p_internal->state = STATE_EXITED; - pthread_exit(status); + if ( p_internal->GetState() != STATE_RUNNING ) + { + wxLogDebug("Can't pause thread which is not running."); + + return wxTHREAD_NOT_RUNNING; + } + + p_internal->SetState(STATE_PAUSED); + + return wxTHREAD_NO_ERROR; } -void wxThread::TestDestroy() +wxThreadError wxThread::Resume() { - pthread_testcancel(); + wxCriticalSectionLocker lock(m_critsect); + + if ( p_internal->GetState() == STATE_PAUSED ) + { + p_internal->Resume(); + + return wxTHREAD_NO_ERROR; + } + else + { + wxLogDebug("Attempt to resume a thread which is not paused."); + + return wxTHREAD_MISC_ERROR; + } } -bool wxThread::IsMain() +// ----------------------------------------------------------------------------- +// exiting thread +// ----------------------------------------------------------------------------- + +wxThread::ExitCode wxThread::Delete() { - return (bool)pthread_equal(pthread_self(), p_mainid); + m_critsect.Enter(); + thread_state state = p_internal->GetState(); + m_critsect.Leave(); + + switch ( state ) + { + case STATE_NEW: + case STATE_EXITED: + // nothing to do + break; + + case STATE_PAUSED: + // resume the thread first + Resume(); + + // fall through + + default: + // set the flag telling to the thread to stop and wait + p_internal->Cancel(); + } + + return NULL; } -wxThread::wxThread() +wxThreadError wxThread::Kill() +{ + switch ( p_internal->GetState() ) + { + case STATE_NEW: + case STATE_EXITED: + return wxTHREAD_NOT_RUNNING; + + default: +#ifdef HAVE_PTHREAD_CANCEL + if ( pthread_cancel(p_internal->GetId()) != 0 ) +#endif + { + wxLogError(_("Failed to terminate a thread.")); + + return wxTHREAD_MISC_ERROR; + } + + return wxTHREAD_NO_ERROR; + } +} + +void wxThread::Exit(void *status) { - p_internal = new wxThreadInternal(); + // first call user-level clean up code + OnExit(); + + // next wake up the threads waiting for us (OTOH, this function won't return + // until someone waited for us!) + p_internal->SignalExit(); + + p_internal->SetState(STATE_EXITED); + + // delete both C++ thread object and terminate the OS thread object + delete this; + pthread_exit(status); +} + +// also test whether we were paused +bool wxThread::TestDestroy() +{ + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); + + if ( p_internal->GetState() == STATE_PAUSED ) + { + // leave the crit section or the other threads will stop too if they try + // to call any of (seemingly harmless) IsXXX() functions while we sleep + m_critsect.Leave(); + + p_internal->Pause(); + + // enter it back before it's finally left in lock object dtor + m_critsect.Enter(); + } + + return p_internal->WasCancelled(); } wxThread::~wxThread() { - Destroy(); - Join(); - delete p_internal; + // remove this thread from the global array + gs_allThreads.Remove(this); } -// The default callback just joins the thread and throws away the result. -void wxThread::OnExit() +// ----------------------------------------------------------------------------- +// state tests +// ----------------------------------------------------------------------------- + +bool wxThread::IsRunning() const { + wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect); + + return p_internal->GetState() == STATE_RUNNING; } -// Automatic initialization -class wxThreadModule : public wxModule { - DECLARE_DYNAMIC_CLASS(wxThreadModule) +bool wxThread::IsAlive() const +{ + wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect); + + switch ( p_internal->GetState() ) + { + case STATE_RUNNING: + case STATE_PAUSED: + return TRUE; + + default: + return FALSE; + } +} + +//-------------------------------------------------------------------- +// wxThreadModule +//-------------------------------------------------------------------- + +class wxThreadModule : public wxModule +{ public: - virtual bool OnInit() { + virtual bool OnInit(); + virtual void OnExit(); + +private: + DECLARE_DYNAMIC_CLASS(wxThreadModule) +}; + +IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) + +bool wxThreadModule::OnInit() +{ + if ( pthread_key_create(&gs_keySelf, NULL /* dtor function */) != 0 ) + { + wxLogError(_("Thread module initialization failed: " + "failed to create pthread key.")); + + return FALSE; + } + + gs_mutexGui = new wxMutex(); wxThreadGuiInit(); - p_mainid = pthread_self(); - wxMainMutex.Lock(); + gs_tidMain = pthread_self(); + gs_mutexGui->Lock(); return TRUE; - } +} + +void wxThreadModule::OnExit() +{ + wxASSERT_MSG( wxThread::IsMain(), "only main thread can be here" ); + + // terminate any threads left + size_t count = gs_allThreads.GetCount(); + if ( count != 0u ) + wxLogDebug("Some threads were not terminated by the application."); - virtual void OnExit() { - wxMainMutex.Unlock(); + for ( size_t n = 0u; n < count; n++ ) + { + gs_allThreads[n]->Delete(); + } + + // destroy GUI mutex + gs_mutexGui->Unlock(); wxThreadGuiExit(); - } -}; + delete gs_mutexGui; -IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule) + // and free TLD slot + (void)pthread_key_delete(gs_keySelf); +}