]> git.saurik.com Git - wxWidgets.git/blob - src/unix/threadpsx.cpp
Empty notebooks show up again. I'm afraid this
[wxWidgets.git] / src / unix / threadpsx.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: threadpsx.cpp
3 // Purpose: wxThread (Posix) Implementation
4 // Author: Original from Wolfram Gloger/Guilhem Lavaux
5 // Modified by:
6 // Created: 04/22/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Wolfram Gloger (1996, 1997)
9 // Guilhem Lavaux (1998)
10 // Vadim Zeitlin (1999)
11 // Robert Roebling (1999)
12 // Licence: wxWindows licence
13 /////////////////////////////////////////////////////////////////////////////
14
15 // ============================================================================
16 // declaration
17 // ============================================================================
18
19 // ----------------------------------------------------------------------------
20 // headers
21 // ----------------------------------------------------------------------------
22
23 #ifdef __GNUG__
24 #pragma implementation "thread.h"
25 #endif
26
27 // With simple makefiles, we must ignore the file body if not using
28 // threads.
29 #include "wx/setup.h"
30
31 #if wxUSE_THREADS
32
33 #include "wx/thread.h"
34 #include "wx/module.h"
35 #include "wx/utils.h"
36 #include "wx/log.h"
37 #include "wx/intl.h"
38 #include "wx/dynarray.h"
39
40 #include <stdio.h>
41 #include <unistd.h>
42 #include <pthread.h>
43 #include <errno.h>
44 #include <time.h>
45
46 #if HAVE_SCHED_H
47 #include <sched.h>
48 #endif
49
50 // ----------------------------------------------------------------------------
51 // constants
52 // ----------------------------------------------------------------------------
53
54 // the possible states of the thread and transitions from them
55 enum wxThreadState
56 {
57 STATE_NEW, // didn't start execution yet (=> RUNNING)
58 STATE_RUNNING, // running (=> PAUSED or EXITED)
59 STATE_PAUSED, // suspended (=> RUNNING or EXITED)
60 STATE_EXITED // thread doesn't exist any more
61 };
62
63 // ----------------------------------------------------------------------------
64 // types
65 // ----------------------------------------------------------------------------
66
67 WX_DEFINE_ARRAY(wxThread *, wxArrayThread);
68
69 // -----------------------------------------------------------------------------
70 // global data
71 // -----------------------------------------------------------------------------
72
73 // we keep the list of all threads created by the application to be able to
74 // terminate them on exit if there are some left - otherwise the process would
75 // be left in memory
76 static wxArrayThread gs_allThreads;
77
78 // the id of the main thread
79 static pthread_t gs_tidMain;
80
81 // the key for the pointer to the associated wxThread object
82 static pthread_key_t gs_keySelf;
83
84 // this mutex must be acquired before any call to a GUI function
85 static wxMutex *gs_mutexGui;
86
87 // ============================================================================
88 // implementation
89 // ============================================================================
90
91 //--------------------------------------------------------------------
92 // wxMutex (Posix implementation)
93 //--------------------------------------------------------------------
94
95 class wxMutexInternal
96 {
97 public:
98 pthread_mutex_t p_mutex;
99 };
100
101 wxMutex::wxMutex()
102 {
103 p_internal = new wxMutexInternal;
104 pthread_mutex_init( &(p_internal->p_mutex), (const pthread_mutexattr_t*) NULL );
105 m_locked = 0;
106 }
107
108 wxMutex::~wxMutex()
109 {
110 if (m_locked > 0)
111 wxLogDebug(_T("Freeing a locked mutex (%d locks)"), m_locked);
112
113 pthread_mutex_destroy( &(p_internal->p_mutex) );
114 delete p_internal;
115 }
116
117 wxMutexError wxMutex::Lock()
118 {
119 int err = pthread_mutex_lock( &(p_internal->p_mutex) );
120 if (err == EDEADLK)
121 {
122 wxLogDebug(_T("Locking this mutex would lead to deadlock!"));
123
124 return wxMUTEX_DEAD_LOCK;
125 }
126
127 m_locked++;
128
129 return wxMUTEX_NO_ERROR;
130 }
131
132 wxMutexError wxMutex::TryLock()
133 {
134 if (m_locked)
135 {
136 return wxMUTEX_BUSY;
137 }
138
139 int err = pthread_mutex_trylock( &(p_internal->p_mutex) );
140 switch (err)
141 {
142 case EBUSY: return wxMUTEX_BUSY;
143 }
144
145 m_locked++;
146
147 return wxMUTEX_NO_ERROR;
148 }
149
150 wxMutexError wxMutex::Unlock()
151 {
152 if (m_locked > 0)
153 {
154 m_locked--;
155 }
156 else
157 {
158 wxLogDebug(_T("Unlocking not locked mutex."));
159
160 return wxMUTEX_UNLOCKED;
161 }
162
163 pthread_mutex_unlock( &(p_internal->p_mutex) );
164
165 return wxMUTEX_NO_ERROR;
166 }
167
168 //--------------------------------------------------------------------
169 // wxCondition (Posix implementation)
170 //--------------------------------------------------------------------
171
172 class wxConditionInternal
173 {
174 public:
175 pthread_cond_t p_condition;
176 };
177
178 wxCondition::wxCondition()
179 {
180 p_internal = new wxConditionInternal;
181 pthread_cond_init( &(p_internal->p_condition), (const pthread_condattr_t *) NULL );
182 }
183
184 wxCondition::~wxCondition()
185 {
186 pthread_cond_destroy( &(p_internal->p_condition) );
187
188 delete p_internal;
189 }
190
191 void wxCondition::Wait(wxMutex& mutex)
192 {
193 pthread_cond_wait( &(p_internal->p_condition), &(mutex.p_internal->p_mutex) );
194 }
195
196 bool wxCondition::Wait(wxMutex& mutex, unsigned long sec, unsigned long nsec)
197 {
198 struct timespec tspec;
199
200 tspec.tv_sec = time(0L)+sec;
201 tspec.tv_nsec = nsec;
202 return (pthread_cond_timedwait(&(p_internal->p_condition), &(mutex.p_internal->p_mutex), &tspec) != ETIMEDOUT);
203 }
204
205 void wxCondition::Signal()
206 {
207 pthread_cond_signal( &(p_internal->p_condition) );
208 }
209
210 void wxCondition::Broadcast()
211 {
212 pthread_cond_broadcast( &(p_internal->p_condition) );
213 }
214
215 //--------------------------------------------------------------------
216 // wxThread (Posix implementation)
217 //--------------------------------------------------------------------
218
219 class wxThreadInternal
220 {
221 public:
222 wxThreadInternal();
223 ~wxThreadInternal();
224
225 // thread entry function
226 static void *PthreadStart(void *ptr);
227
228 #if HAVE_THREAD_CLEANUP_FUNCTIONS
229 // thread exit function
230 static void PthreadCleanup(void *ptr);
231 #endif
232
233 // thread actions
234 // start the thread
235 wxThreadError Run();
236 // ask the thread to terminate
237 void Wait();
238 // wake up threads waiting for our termination
239 void SignalExit();
240 // go to sleep until Resume() is called
241 void Pause();
242 // resume the thread
243 void Resume();
244
245 // accessors
246 // priority
247 int GetPriority() const { return m_prio; }
248 void SetPriority(int prio) { m_prio = prio; }
249 // state
250 wxThreadState GetState() const { return m_state; }
251 void SetState(wxThreadState state) { m_state = state; }
252 // id
253 pthread_t GetId() const { return m_threadId; }
254 pthread_t *GetIdPtr() { return &m_threadId; }
255 // "cancelled" flag
256 void SetCancelFlag() { m_cancelled = TRUE; }
257 bool WasCancelled() const { return m_cancelled; }
258
259 private:
260 pthread_t m_threadId; // id of the thread
261 wxThreadState m_state; // see wxThreadState enum
262 int m_prio; // in wxWindows units: from 0 to 100
263
264 // set when the thread should terminate
265 bool m_cancelled;
266
267 // this (mutex, cond) pair is used to synchronize the main thread and this
268 // thread in several situations:
269 // 1. The thread function blocks until condition is signaled by Run() when
270 // it's initially created - this allows thread creation in "suspended"
271 // state
272 // 2. The Delete() function blocks until the condition is signaled when the
273 // thread exits.
274 wxMutex m_mutex, m_end_mutex;
275 wxCondition m_cond;
276
277 // another (mutex, cond) pair for Pause()/Resume() usage
278 //
279 // VZ: it's possible that we might reuse the mutex and condition from above
280 // for this too, but as I'm not at all sure that it won't create subtle
281 // problems with race conditions between, say, Pause() and Delete() I
282 // prefer this may be a bit less efficient but much safer solution
283 wxMutex m_mutexSuspend;
284 wxCondition m_condSuspend;
285 };
286
287 void *wxThreadInternal::PthreadStart(void *ptr)
288 {
289 wxThread *thread = (wxThread *)ptr;
290 wxThreadInternal *pthread = thread->p_internal;
291 void *status;
292
293 int rc = pthread_setspecific(gs_keySelf, thread);
294 if ( rc != 0 )
295 {
296 wxLogSysError(rc, _("Cannot start thread: error writing TLS"));
297
298 return (void *)-1;
299 }
300 #if HAVE_THREAD_CLEANUP_FUNCTIONS
301 // Install the cleanup handler.
302 // pthread_cleanup_push(wxThreadInternal::PthreadCleanup, ptr);
303 #endif
304
305 // wait for the condition to be signaled from Run()
306 // mutex state: currently locked by the thread which created us
307 pthread->m_cond.Wait(pthread->m_mutex);
308 // mutex state: locked again on exit of Wait()
309
310 // call the main entry
311 status = thread->Entry();
312
313 #if HAVE_THREAD_CLEANUP_FUNCTIONS
314 // pthread_cleanup_pop(FALSE);
315 #endif
316
317 // terminate the thread
318 thread->Exit(status);
319
320 wxFAIL_MSG(_T("wxThread::Exit() can't return."));
321
322 return NULL;
323 }
324
325 #if HAVE_THREAD_CLEANUP_FUNCTIONS
326 // Only called when the thread is explicitely killed.
327
328 void wxThreadInternal::PthreadCleanup(void *ptr)
329 {
330 wxThread *thread = (wxThread *) ptr;
331
332 // The thread is already considered as finished.
333 if (thread->p_internal->GetState() == STATE_EXITED)
334 return;
335
336 // first call user-level clean up code
337 thread->OnExit();
338
339 // next wake up the threads waiting for us (OTOH, this function won't retur
340 // until someone waited for us!)
341 thread->p_internal->SetState(STATE_EXITED);
342
343 thread->p_internal->SignalExit();
344 }
345 #endif
346
347 wxThreadInternal::wxThreadInternal()
348 {
349 m_state = STATE_NEW;
350 m_cancelled = FALSE;
351
352 // this mutex is locked during almost all thread lifetime - it will only be
353 // unlocked in the very end
354 m_mutex.Lock();
355
356 // this mutex is used by wxThreadInternal::Wait() and by
357 // wxThreadInternal::SignalExit(). We don't use m_mutex because of a
358 // possible deadlock in either Wait() or SignalExit().
359 m_end_mutex.Lock();
360
361 // this mutex is used in Pause()/Resume() and is also locked all the time
362 // unless the thread is paused
363 m_mutexSuspend.Lock();
364 }
365
366 wxThreadInternal::~wxThreadInternal()
367 {
368 // GL: moved to SignalExit
369 // m_mutexSuspend.Unlock();
370
371 // note that m_mutex will be unlocked by the thread which waits for our
372 // termination
373
374 // m_end_mutex can be unlocked here.
375 m_end_mutex.Unlock();
376 }
377
378 wxThreadError wxThreadInternal::Run()
379 {
380 wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING,
381 _T("thread may only be started once after successful Create()") );
382
383 // the mutex was locked on Create(), so we will be able to lock it again
384 // only when the thread really starts executing and enters the wait -
385 // otherwise we might signal the condition before anybody is waiting for it
386 wxMutexLocker lock(m_mutex);
387 m_cond.Signal();
388
389 m_state = STATE_RUNNING;
390
391 return wxTHREAD_NO_ERROR;
392
393 // now the mutex is unlocked back - but just to allow Wait() function to
394 // terminate by relocking it, so the net result is that the worker thread
395 // starts executing and the mutex is still locked
396 }
397
398 void wxThreadInternal::Wait()
399 {
400 wxCHECK_RET( WasCancelled(), _T("thread should have been cancelled first") );
401
402 // if the thread we're waiting for is waiting for the GUI mutex, we will
403 // deadlock so make sure we release it temporarily
404 if ( wxThread::IsMain() )
405 wxMutexGuiLeave();
406
407 // entering Wait() releases the mutex thus allowing SignalExit() to acquire
408 // it and to signal us its termination
409 m_cond.Wait(m_end_mutex);
410
411 // mutex is still in the locked state - relocked on exit from Wait(), so
412 // unlock it - we don't need it any more, the thread has already terminated
413 m_end_mutex.Unlock();
414
415 // After that, we wait for the real end of the other thread.
416 pthread_join(GetId(), NULL);
417
418 // reacquire GUI mutex
419 if ( wxThread::IsMain() )
420 wxMutexGuiEnter();
421 }
422
423 void wxThreadInternal::SignalExit()
424 {
425 // GL: Unlock mutexSuspend here.
426 m_mutexSuspend.Unlock();
427
428 // as mutex is currently locked, this will block until some other thread
429 // (normally the same which created this one) unlocks it by entering Wait()
430 m_end_mutex.Lock();
431
432 // wake up all the threads waiting for our termination
433 m_cond.Broadcast();
434
435 // after this call mutex will be finally unlocked
436 m_end_mutex.Unlock();
437 }
438
439 void wxThreadInternal::Pause()
440 {
441 // the state is set from the thread which pauses us first, this function
442 // is called later so the state should have been already set
443 wxCHECK_RET( m_state == STATE_PAUSED,
444 _T("thread must first be paused with wxThread::Pause().") );
445
446 // don't pause the thread which is being terminated - this would lead to
447 // deadlock if the thread is paused after Delete() had called Resume() but
448 // before it had time to call Wait()
449 if ( WasCancelled() )
450 return;
451
452 // wait until the condition is signaled from Resume()
453 m_condSuspend.Wait(m_mutexSuspend);
454 }
455
456 void wxThreadInternal::Resume()
457 {
458 wxCHECK_RET( m_state == STATE_PAUSED,
459 _T("can't resume thread which is not suspended.") );
460
461 // we will be able to lock this mutex only when Pause() starts waiting
462 wxMutexLocker lock(m_mutexSuspend);
463 m_condSuspend.Signal();
464
465 SetState(STATE_RUNNING);
466 }
467
468 // -----------------------------------------------------------------------------
469 // static functions
470 // -----------------------------------------------------------------------------
471
472 wxThread *wxThread::This()
473 {
474 return (wxThread *)pthread_getspecific(gs_keySelf);
475 }
476
477 bool wxThread::IsMain()
478 {
479 return (bool)pthread_equal(pthread_self(), gs_tidMain);
480 }
481
482 void wxThread::Yield()
483 {
484 sched_yield();
485 }
486
487 void wxThread::Sleep(unsigned long milliseconds)
488 {
489 wxUsleep(milliseconds);
490 }
491
492 // -----------------------------------------------------------------------------
493 // creating thread
494 // -----------------------------------------------------------------------------
495
496 wxThread::wxThread()
497 {
498 // add this thread to the global list of all threads
499 gs_allThreads.Add(this);
500
501 p_internal = new wxThreadInternal();
502 }
503
504 wxThreadError wxThread::Create()
505 {
506 if (p_internal->GetState() != STATE_NEW)
507 return wxTHREAD_RUNNING;
508
509 // set up the thread attribute: right now, we only set thread priority
510 pthread_attr_t attr;
511 pthread_attr_init(&attr);
512
513 #ifdef HAVE_THREAD_PRIORITY_FUNCTIONS
514 int prio;
515 if ( pthread_attr_getschedpolicy(&attr, &prio) != 0 )
516 {
517 wxLogError(_("Cannot retrieve thread scheduling policy."));
518 }
519
520 int min_prio = sched_get_priority_min(prio),
521 max_prio = sched_get_priority_max(prio);
522
523 if ( min_prio == -1 || max_prio == -1 )
524 {
525 wxLogError(_("Cannot get priority range for scheduling policy %d."),
526 prio);
527 }
528 else
529 {
530 struct sched_param sp;
531 pthread_attr_getschedparam(&attr, &sp);
532 sp.sched_priority = min_prio +
533 (p_internal->GetPriority()*(max_prio-min_prio))/100;
534 pthread_attr_setschedparam(&attr, &sp);
535 }
536 #endif // HAVE_THREAD_PRIORITY_FUNCTIONS
537
538 #ifdef HAVE_PTHREAD_ATTR_SETSCOPE
539 // this will make the threads created by this process really concurrent
540 pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
541 #endif // HAVE_PTHREAD_ATTR_SETSCOPE
542
543 // create the new OS thread object
544 int rc = pthread_create(p_internal->GetIdPtr(), &attr,
545 wxThreadInternal::PthreadStart, (void *)this);
546 pthread_attr_destroy(&attr);
547
548 if ( rc != 0 )
549 {
550 p_internal->SetState(STATE_EXITED);
551 return wxTHREAD_NO_RESOURCE;
552 }
553
554 return wxTHREAD_NO_ERROR;
555 }
556
557 wxThreadError wxThread::Run()
558 {
559 return p_internal->Run();
560 }
561
562 // -----------------------------------------------------------------------------
563 // misc accessors
564 // -----------------------------------------------------------------------------
565
566 void wxThread::SetPriority(unsigned int prio)
567 {
568 wxCHECK_RET( ((int)WXTHREAD_MIN_PRIORITY <= (int)prio) &&
569 ((int)prio <= (int)WXTHREAD_MAX_PRIORITY),
570 _T("invalid thread priority") );
571
572 wxCriticalSectionLocker lock(m_critsect);
573
574 switch ( p_internal->GetState() )
575 {
576 case STATE_NEW:
577 // thread not yet started, priority will be set when it is
578 p_internal->SetPriority(prio);
579 break;
580
581 case STATE_RUNNING:
582 case STATE_PAUSED:
583 #ifdef HAVE_THREAD_PRIORITY_FUNCTIONS
584 {
585 struct sched_param sparam;
586 sparam.sched_priority = prio;
587
588 if ( pthread_setschedparam(p_internal->GetId(),
589 SCHED_OTHER, &sparam) != 0 )
590 {
591 wxLogError(_("Failed to set thread priority %d."), prio);
592 }
593 }
594 #endif // HAVE_THREAD_PRIORITY_FUNCTIONS
595 break;
596
597 case STATE_EXITED:
598 default:
599 wxFAIL_MSG(_T("impossible to set thread priority in this state"));
600 }
601 }
602
603 unsigned int wxThread::GetPriority() const
604 {
605 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
606
607 return p_internal->GetPriority();
608 }
609
610 unsigned long wxThread::GetID() const
611 {
612 return (unsigned long)p_internal->GetId();
613 }
614
615 // -----------------------------------------------------------------------------
616 // pause/resume
617 // -----------------------------------------------------------------------------
618
619 wxThreadError wxThread::Pause()
620 {
621 wxCriticalSectionLocker lock(m_critsect);
622
623 if ( p_internal->GetState() != STATE_RUNNING )
624 {
625 wxLogDebug(_T("Can't pause thread which is not running."));
626
627 return wxTHREAD_NOT_RUNNING;
628 }
629
630 p_internal->SetState(STATE_PAUSED);
631
632 return wxTHREAD_NO_ERROR;
633 }
634
635 wxThreadError wxThread::Resume()
636 {
637 wxCriticalSectionLocker lock(m_critsect);
638
639 if ( p_internal->GetState() == STATE_PAUSED )
640 {
641 m_critsect.Leave();
642 p_internal->Resume();
643 m_critsect.Enter();
644
645 return wxTHREAD_NO_ERROR;
646 }
647 else
648 {
649 wxLogDebug(_T("Attempt to resume a thread which is not paused."));
650
651 return wxTHREAD_MISC_ERROR;
652 }
653 }
654
655 // -----------------------------------------------------------------------------
656 // exiting thread
657 // -----------------------------------------------------------------------------
658
659 wxThread::ExitCode wxThread::Delete()
660 {
661 m_critsect.Enter();
662 wxThreadState state = p_internal->GetState();
663
664 // ask the thread to stop
665 p_internal->SetCancelFlag();
666
667 m_critsect.Leave();
668
669 switch ( state )
670 {
671 case STATE_NEW:
672 case STATE_EXITED:
673 // nothing to do
674 break;
675
676 case STATE_PAUSED:
677 // resume the thread first
678 Resume();
679
680 // fall through
681
682 default:
683 // wait until the thread stops
684 p_internal->Wait();
685 }
686
687 return NULL;
688 }
689
690 wxThreadError wxThread::Kill()
691 {
692 switch ( p_internal->GetState() )
693 {
694 case STATE_NEW:
695 case STATE_EXITED:
696 return wxTHREAD_NOT_RUNNING;
697
698 default:
699 #ifdef HAVE_PTHREAD_CANCEL
700 if ( pthread_cancel(p_internal->GetId()) != 0 )
701 #endif
702 {
703 wxLogError(_("Failed to terminate a thread."));
704
705 return wxTHREAD_MISC_ERROR;
706 }
707
708 return wxTHREAD_NO_ERROR;
709 }
710 }
711
712 void wxThread::Exit(void *status)
713 {
714 // first call user-level clean up code
715 OnExit();
716
717 // next wake up the threads waiting for us (OTOH, this function won't return
718 // until someone waited for us!)
719 p_internal->SignalExit();
720
721 p_internal->SetState(STATE_EXITED);
722
723 // delete both C++ thread object and terminate the OS thread object
724 // GL: This is very ugly and buggy ...
725 // delete this;
726 pthread_exit(status);
727 }
728
729 // also test whether we were paused
730 bool wxThread::TestDestroy()
731 {
732 wxCriticalSectionLocker lock(m_critsect);
733
734 if ( p_internal->GetState() == STATE_PAUSED )
735 {
736 // leave the crit section or the other threads will stop too if they try
737 // to call any of (seemingly harmless) IsXXX() functions while we sleep
738 m_critsect.Leave();
739
740 p_internal->Pause();
741
742 // enter it back before it's finally left in lock object dtor
743 m_critsect.Enter();
744 }
745
746 return p_internal->WasCancelled();
747 }
748
749 wxThread::~wxThread()
750 {
751 // remove this thread from the global array
752 gs_allThreads.Remove(this);
753 }
754
755 // -----------------------------------------------------------------------------
756 // state tests
757 // -----------------------------------------------------------------------------
758
759 bool wxThread::IsRunning() const
760 {
761 wxCriticalSectionLocker lock((wxCriticalSection &)m_critsect);
762
763 return p_internal->GetState() == STATE_RUNNING;
764 }
765
766 bool wxThread::IsAlive() const
767 {
768 wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect);
769
770 switch ( p_internal->GetState() )
771 {
772 case STATE_RUNNING:
773 case STATE_PAUSED:
774 return TRUE;
775
776 default:
777 return FALSE;
778 }
779 }
780
781 bool wxThread::IsPaused() const
782 {
783 wxCriticalSectionLocker lock((wxCriticalSection&)m_critsect);
784
785 return (p_internal->GetState() == STATE_PAUSED);
786 }
787
788 //--------------------------------------------------------------------
789 // wxThreadModule
790 //--------------------------------------------------------------------
791
792 class wxThreadModule : public wxModule
793 {
794 public:
795 virtual bool OnInit();
796 virtual void OnExit();
797
798 private:
799 DECLARE_DYNAMIC_CLASS(wxThreadModule)
800 };
801
802 IMPLEMENT_DYNAMIC_CLASS(wxThreadModule, wxModule)
803
804 bool wxThreadModule::OnInit()
805 {
806 int rc = pthread_key_create(&gs_keySelf, NULL /* dtor function */);
807 if ( rc != 0 )
808 {
809 wxLogSysError(rc, _("Thread module initialization failed: "
810 "failed to create thread key"));
811
812 return FALSE;
813 }
814
815 gs_mutexGui = new wxMutex();
816
817 gs_tidMain = pthread_self();
818
819 gs_mutexGui->Lock();
820
821 return TRUE;
822 }
823
824 void wxThreadModule::OnExit()
825 {
826 wxASSERT_MSG( wxThread::IsMain(), _T("only main thread can be here") );
827
828 // terminate any threads left
829 size_t count = gs_allThreads.GetCount();
830 if ( count != 0u )
831 wxLogDebug(_T("Some threads were not terminated by the application."));
832
833 for ( size_t n = 0u; n < count; n++ )
834 {
835 gs_allThreads[n]->Delete();
836 }
837
838 // destroy GUI mutex
839 gs_mutexGui->Unlock();
840
841 delete gs_mutexGui;
842
843 // and free TLD slot
844 (void)pthread_key_delete(gs_keySelf);
845 }
846
847 // ----------------------------------------------------------------------------
848 // global functions
849 // ----------------------------------------------------------------------------
850
851 void wxMutexGuiEnter()
852 {
853 gs_mutexGui->Lock();
854 }
855
856 void wxMutexGuiLeave()
857 {
858 gs_mutexGui->Unlock();
859 }
860
861 #endif
862 // wxUSE_THREADS