From: Vadim Zeitlin Date: Fri, 22 Jan 1999 16:21:24 +0000 (+0000) Subject: wxThread POSIX implementation seems to work (under libc6 Linux at least) X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/7c3d7e2d777b4c5892adadf3e658b8923645ae04 wxThread POSIX implementation seems to work (under libc6 Linux at least) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@1449 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/include/wx/thread.h b/include/wx/thread.h index d37d81a733..cb3f019236 100644 --- a/include/wx/thread.h +++ b/include/wx/thread.h @@ -105,14 +105,16 @@ class WXDLLEXPORT wxMutexLocker { public: // lock the mutex in the ctor - wxMutexLocker(wxMutex *mutex) - { m_isOk = mutex && ((m_mutex = mutex)->Lock() == wxMUTEX_NO_ERROR); } + wxMutexLocker(wxMutex& mutex) : m_mutex(mutex) + { m_isOk = m_mutex.Lock() == wxMUTEX_NO_ERROR; } // returns TRUE if mutex was successfully locked in ctor - bool IsOk() const { return m_isOk; } + bool IsOk() const + { return m_isOk; } // unlock the mutex in dtor - ~wxMutexLocker() { if ( IsOk() ) m_mutex->Unlock(); } + ~wxMutexLocker() + { if ( IsOk() ) m_mutex.Unlock(); } private: // no assignment operator nor copy ctor @@ -120,7 +122,7 @@ private: wxMutexLocker& operator=(const wxMutexLocker&); bool m_isOk; - wxMutex *m_mutex; + wxMutex& m_mutex; }; // ---------------------------------------------------------------------------- diff --git a/samples/thread/test.cpp b/samples/thread/test.cpp index f8c872f879..dce914e8e6 100644 --- a/samples/thread/test.cpp +++ b/samples/thread/test.cpp @@ -48,6 +48,7 @@ public: bool OnInit(); }; +class MyThread; WX_DEFINE_ARRAY(wxThread *, wxArrayThread); // Define a new frame type @@ -66,6 +67,7 @@ public: void OnClear(wxCommandEvent& event); void OnStartThread(wxCommandEvent& event); + void OnStartThreads(wxCommandEvent& event); void OnStopThread(wxCommandEvent& event); void OnPauseThread(wxCommandEvent& event); void OnResumeThread(wxCommandEvent& event); @@ -73,23 +75,38 @@ public: void OnIdle(wxIdleEvent &event); bool OnClose() { return TRUE; } - // called by dying thread + // called by dying thread _in_that_thread_context_ void OnThreadExit(wxThread *thread); -public: - wxArrayThread m_threads; - private: - // crit section protects access to the array below + // helper function - creates a new thread (but doesn't run it) + MyThread *CreateThread(); + + // crit section protects access to all of the arrays below wxCriticalSection m_critsect; + // all the threads currently alive - as soon as the thread terminates, it's + // removed from the array + wxArrayThread m_threads; + + // both of these arrays are only valid between 2 iterations of OnIdle(), + // they're cleared each time it is excuted. + // the array of threads which finished (either because they did their work // or because they were explicitly stopped) - wxArrayInt m_aToDelete; + wxArrayThread m_terminated; + // the array of threads which were stopped by the user and not terminated + // by themselves - these threads shouldn't be Delete()d second time from + // OnIdle() + wxArrayThread m_stopped; + // just some place to put our messages in wxTextCtrl *m_txtctrl; + // remember the number of running threads and total number of threads + size_t m_nRunning, m_nCount; + DECLARE_EVENT_TABLE() }; @@ -168,12 +185,13 @@ enum { TEST_QUIT = 1, TEST_TEXT = 101, - TEST_ABOUT = 102, - TEST_CLEAR = 103, - TEST_START_THREAD = 203, - TEST_STOP_THREAD = 204, - TEST_PAUSE_THREAD = 205, - TEST_RESUME_THREAD = 206 + TEST_ABOUT, + TEST_CLEAR, + TEST_START_THREAD = 201, + TEST_START_THREADS, + TEST_STOP_THREAD, + TEST_PAUSE_THREAD, + TEST_RESUME_THREAD }; BEGIN_EVENT_TABLE(MyFrame, wxFrame) @@ -181,6 +199,7 @@ BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(TEST_ABOUT, MyFrame::OnAbout) EVT_MENU(TEST_CLEAR, MyFrame::OnClear) EVT_MENU(TEST_START_THREAD, MyFrame::OnStartThread) + EVT_MENU(TEST_START_THREADS, MyFrame::OnStartThreads) EVT_MENU(TEST_STOP_THREAD, MyFrame::OnStopThread) EVT_MENU(TEST_PAUSE_THREAD, MyFrame::OnPauseThread) EVT_MENU(TEST_RESUME_THREAD, MyFrame::OnResumeThread) @@ -211,6 +230,7 @@ bool MyApp::OnInit() wxMenu *thread_menu = new wxMenu; thread_menu->Append(TEST_START_THREAD, "&Start a new thread"); + thread_menu->Append(TEST_START_THREADS, "Start &many threads at once"); thread_menu->Append(TEST_STOP_THREAD, "S&top a running thread"); thread_menu->AppendSeparator(); thread_menu->Append(TEST_PAUSE_THREAD, "&Pause a running thread"); @@ -231,14 +251,16 @@ MyFrame::MyFrame(wxFrame *frame, const wxString& title, int x, int y, int w, int h) : wxFrame(frame, -1, title, wxPoint(x, y), wxSize(w, h)) { - CreateStatusBar(); + m_nRunning = m_nCount = 0; + + CreateStatusBar(2); m_txtctrl = new wxTextCtrl(this, -1, "", wxPoint(0, 0), wxSize(0, 0), wxTE_MULTILINE | wxTE_READONLY); } -void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) +MyThread *MyFrame::CreateThread() { MyThread *thread = new MyThread(this); @@ -250,10 +272,52 @@ void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) wxCriticalSectionLocker enter(m_critsect); m_threads.Add(thread); + return thread; +} + +void MyFrame::OnStartThreads(wxCommandEvent& WXUNUSED(event) ) +{ + static wxString s_str; + s_str = wxGetTextFromUser("How many threads to start: ", + "wxThread sample", + s_str, this); + if ( s_str.IsEmpty() ) + return; + + size_t count, n; + sscanf(s_str, "%u", &count); + if ( count == 0 ) + return; + + wxArrayThread threads; + + // first create them all... + for ( n = 0; n < count; n++ ) + { + threads.Add(CreateThread()); + } + + wxString msg; + msg.Printf("%d new threads created.", count); + SetStatusText(msg, 1); + + // ...and then start them + for ( n = 0; n < count; n++ ) + { + threads[n]->Run(); + } +} + +void MyFrame::OnStartThread(wxCommandEvent& WXUNUSED(event) ) +{ + MyThread *thread = CreateThread(); + if ( thread->Run() != wxTHREAD_NO_ERROR ) { wxLogError("Can't start thread!"); } + + SetStatusText("New thread started.", 1); } void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) @@ -265,7 +329,19 @@ void MyFrame::OnStopThread(wxCommandEvent& WXUNUSED(event) ) } else { - m_threads.Last()->Delete(); + m_critsect.Enter(); + + wxThread *thread = m_threads.Last(); + m_stopped.Add(thread); + + // it's important to leave critical section before calling Delete() + // because delete will (implicitly) call OnThreadExit() which also tries + // to enter the same crit section - would dead lock. + m_critsect.Leave(); + + thread->Delete(); + + SetStatusText("Thread stopped.", 1); } } @@ -279,9 +355,15 @@ void MyFrame::OnResumeThread(wxCommandEvent& WXUNUSED(event) ) n++; if ( n == count ) + { wxLogError("No thread to resume!"); + } else + { m_threads[n]->Resume(); + + SetStatusText("Thread resumed.", 1); + } } void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) ) @@ -294,27 +376,36 @@ void MyFrame::OnPauseThread(wxCommandEvent& WXUNUSED(event) ) n--; if ( n < 0 ) + { wxLogError("No thread to pause!"); + } else + { m_threads[n]->Pause(); + + SetStatusText("Thread paused.", 1); + } } // set the frame title indicating the current number of threads void MyFrame::OnIdle(wxIdleEvent &event) { - // first remove from the array all the threads which died since last call + // first wait for all the threads which dies since the last call { wxCriticalSectionLocker enter(m_critsect); - size_t nCount = m_aToDelete.Count(); + size_t nCount = m_terminated.GetCount(); for ( size_t n = 0; n < nCount; n++ ) { - // index should be shifted by n because we've already deleted - // n-1 elements from the array - m_threads.Remove((size_t)m_aToDelete[n] - n); + // don't delete the threads which were stopped - they were already + // deleted in OnStopThread() + wxThread *thread = m_terminated[n]; + if ( m_stopped.Index(thread) == wxNOT_FOUND ) + thread->Delete(); } - m_aToDelete.Empty(); + m_stopped.Empty(); + m_terminated.Empty(); } size_t nRunning = 0, @@ -325,7 +416,14 @@ void MyFrame::OnIdle(wxIdleEvent &event) nRunning++; } - wxLogStatus(this, "%u threads total, %u running.", nCount, nRunning); + if ( nCount != m_nCount || nRunning != m_nRunning ) + { + m_nRunning = nRunning; + m_nCount = nCount; + + wxLogStatus(this, "%u threads total, %u running.", nCount, nRunning); + } + //else: avoid flicker - don't print anything } void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event) ) @@ -357,10 +455,8 @@ void MyFrame::OnClear(wxCommandEvent& WXUNUSED(event)) void MyFrame::OnThreadExit(wxThread *thread) { - int index = m_threads.Index(thread); - wxCHECK_RET( index != -1, "unknown thread being deleted??" ); - wxCriticalSectionLocker enter(m_critsect); - m_aToDelete.Add(index); + m_threads.Remove(thread); + m_terminated.Add(thread); } diff --git a/src/gtk/threadpsx.cpp b/src/gtk/threadpsx.cpp index 28abd1b362..fba9ca0037 100644 --- a/src/gtk/threadpsx.cpp +++ b/src/gtk/threadpsx.cpp @@ -29,6 +29,7 @@ #include "wx/utils.h" #include "wx/log.h" #include "wx/intl.h" +#include "wx/dynarray.h" #include "gdk/gdk.h" #include "gtk/gtk.h" @@ -42,12 +43,19 @@ enum thread_state 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_pidMain; +static pthread_t gs_tidMain; // the key for the pointer to the associated wxThread object static pthread_key_t gs_keySelf; @@ -81,7 +89,7 @@ wxMutex::wxMutex() wxMutex::~wxMutex() { if (m_locked > 0) - wxLogDebug( "wxMutex warning: freeing a locked mutex (%d locks)", m_locked ); + wxLogDebug("Freeing a locked mutex (%d locks)", m_locked); pthread_mutex_destroy( &(p_internal->p_mutex) ); delete p_internal; @@ -92,6 +100,8 @@ wxMutexError wxMutex::Lock() int err = pthread_mutex_lock( &(p_internal->p_mutex) ); if (err == EDEADLK) { + wxLogDebug("Locking this mutex would lead to deadlock!"); + return wxMUTEX_DEAD_LOCK; } @@ -126,6 +136,8 @@ wxMutexError wxMutex::Unlock() } else { + wxLogDebug("Unlocking not locked mutex."); + return wxMUTEX_UNLOCKED; } @@ -188,14 +200,23 @@ void wxCondition::Broadcast() class wxThreadInternal { public: - wxThreadInternal() { m_state = STATE_NEW; } - ~wxThreadInternal() {} + wxThreadInternal(); + ~wxThreadInternal(); // thread entry function static void *PthreadStart(void *ptr); - // start the thread + // 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 @@ -207,7 +228,6 @@ public: // id pthread_t GetId() const { return thread_id; } // "cancelled" flag - void Cancel(); bool WasCancelled() const { return m_cancelled; } //private: -- should be! @@ -220,13 +240,24 @@ private: // set when the thread should terminate bool m_cancelled; - // we start running when this condition becomes true - wxMutex m_mutexRun; - wxCondition m_condRun; - - // this condition becomes true when we get back to PthreadStart() function - wxMutex m_mutexStop; - wxCondition m_condStop; + // 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) @@ -242,17 +273,14 @@ void *wxThreadInternal::PthreadStart(void *ptr) } // wait for the condition to be signaled from Run() - pthread->m_mutexRun.Lock(); - pthread->m_condRun.Wait(pthread->m_mutexRun); + // 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(); - pthread->m_mutexRun.Unlock(); - - // wake up the pthread(s) waiting for our termination - pthread->m_condStop.Broadcast(); - // terminate the thread thread->Exit(status); @@ -261,22 +289,104 @@ void *wxThreadInternal::PthreadStart(void *ptr) return NULL; } +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() +{ + m_mutexSuspend.Unlock(); + + // note that m_mutex will be unlocked by the thread which waits for our + // termination +} + wxThreadError wxThreadInternal::Run() { wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, "thread may only be started once after successful Create()" ); - wxMutexLocker lock(&m_mutexRun); - m_condRun.Signal(); + // 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(); + + m_state = STATE_RUNNING; 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() { - wxMutexLocker lock(&m_mutexStop); + // 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; - m_condStop.Wait(m_mutexStop); + + // 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 wxThreadInternal::SignalExit() +{ + // 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(); + + // wake up all the threads waiting for our termination + m_cond.Broadcast(); + + // after this call mutex will be finally unlocked + m_mutex.Unlock(); +} + +void wxThreadInternal::Pause() +{ + 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 wxThreadInternal::Resume() +{ + 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); } // ----------------------------------------------------------------------------- @@ -290,7 +400,7 @@ wxThread *wxThread::This() bool wxThread::IsMain() { - return (bool)pthread_equal(pthread_self(), gs_pidMain); + return (bool)pthread_equal(pthread_self(), gs_tidMain); } void wxThread::Yield() @@ -311,6 +421,9 @@ void wxThread::Sleep(unsigned long milliseconds) wxThread::wxThread() { + // add this thread to the global list of all threads + gs_allThreads.Add(this); + p_internal = new wxThreadInternal(); } @@ -346,7 +459,7 @@ wxThreadError wxThread::Create() pthread_attr_setschedparam(&attr, &sp); } - // this is the point of no return + // create the new OS thread object int rc = pthread_create(&p_internal->thread_id, &attr, wxThreadInternal::PthreadStart, (void *)this); pthread_attr_destroy(&attr); @@ -441,7 +554,7 @@ wxThreadError wxThread::Resume() if ( p_internal->GetState() == STATE_PAUSED ) { - p_internal->SetState(STATE_RUNNING); + p_internal->Resume(); return wxTHREAD_NO_ERROR; } @@ -506,27 +619,44 @@ wxThreadError wxThread::Kill() void wxThread::Exit(void *status) { - wxThread *ptr = this; - THREAD_SEND_EXIT_MSG(ptr); - + // 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() const { 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() { + // remove this thread from the global array + gs_allThreads.Remove(this); } // ----------------------------------------------------------------------------- @@ -583,7 +713,7 @@ bool wxThreadModule::OnInit() gs_mutexGui = new wxMutex(); wxThreadGuiInit(); - gs_pidMain = (int)getpid(); + gs_tidMain = pthread_self(); gs_mutexGui->Lock(); return TRUE; @@ -591,9 +721,23 @@ bool wxThreadModule::OnInit() 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."); + + for ( size_t n = 0u; n < count; n++ ) + { + gs_allThreads[n]->Delete(); + } + + // destroy GUI mutex gs_mutexGui->Unlock(); wxThreadGuiExit(); delete gs_mutexGui; + // and free TLD slot (void)pthread_key_delete(gs_keySelf); } diff --git a/src/gtk1/threadpsx.cpp b/src/gtk1/threadpsx.cpp index 28abd1b362..fba9ca0037 100644 --- a/src/gtk1/threadpsx.cpp +++ b/src/gtk1/threadpsx.cpp @@ -29,6 +29,7 @@ #include "wx/utils.h" #include "wx/log.h" #include "wx/intl.h" +#include "wx/dynarray.h" #include "gdk/gdk.h" #include "gtk/gtk.h" @@ -42,12 +43,19 @@ enum thread_state 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_pidMain; +static pthread_t gs_tidMain; // the key for the pointer to the associated wxThread object static pthread_key_t gs_keySelf; @@ -81,7 +89,7 @@ wxMutex::wxMutex() wxMutex::~wxMutex() { if (m_locked > 0) - wxLogDebug( "wxMutex warning: freeing a locked mutex (%d locks)", m_locked ); + wxLogDebug("Freeing a locked mutex (%d locks)", m_locked); pthread_mutex_destroy( &(p_internal->p_mutex) ); delete p_internal; @@ -92,6 +100,8 @@ wxMutexError wxMutex::Lock() int err = pthread_mutex_lock( &(p_internal->p_mutex) ); if (err == EDEADLK) { + wxLogDebug("Locking this mutex would lead to deadlock!"); + return wxMUTEX_DEAD_LOCK; } @@ -126,6 +136,8 @@ wxMutexError wxMutex::Unlock() } else { + wxLogDebug("Unlocking not locked mutex."); + return wxMUTEX_UNLOCKED; } @@ -188,14 +200,23 @@ void wxCondition::Broadcast() class wxThreadInternal { public: - wxThreadInternal() { m_state = STATE_NEW; } - ~wxThreadInternal() {} + wxThreadInternal(); + ~wxThreadInternal(); // thread entry function static void *PthreadStart(void *ptr); - // start the thread + // 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 @@ -207,7 +228,6 @@ public: // id pthread_t GetId() const { return thread_id; } // "cancelled" flag - void Cancel(); bool WasCancelled() const { return m_cancelled; } //private: -- should be! @@ -220,13 +240,24 @@ private: // set when the thread should terminate bool m_cancelled; - // we start running when this condition becomes true - wxMutex m_mutexRun; - wxCondition m_condRun; - - // this condition becomes true when we get back to PthreadStart() function - wxMutex m_mutexStop; - wxCondition m_condStop; + // 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) @@ -242,17 +273,14 @@ void *wxThreadInternal::PthreadStart(void *ptr) } // wait for the condition to be signaled from Run() - pthread->m_mutexRun.Lock(); - pthread->m_condRun.Wait(pthread->m_mutexRun); + // 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(); - pthread->m_mutexRun.Unlock(); - - // wake up the pthread(s) waiting for our termination - pthread->m_condStop.Broadcast(); - // terminate the thread thread->Exit(status); @@ -261,22 +289,104 @@ void *wxThreadInternal::PthreadStart(void *ptr) return NULL; } +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() +{ + m_mutexSuspend.Unlock(); + + // note that m_mutex will be unlocked by the thread which waits for our + // termination +} + wxThreadError wxThreadInternal::Run() { wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING, "thread may only be started once after successful Create()" ); - wxMutexLocker lock(&m_mutexRun); - m_condRun.Signal(); + // 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(); + + m_state = STATE_RUNNING; 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() { - wxMutexLocker lock(&m_mutexStop); + // 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; - m_condStop.Wait(m_mutexStop); + + // 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 wxThreadInternal::SignalExit() +{ + // 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(); + + // wake up all the threads waiting for our termination + m_cond.Broadcast(); + + // after this call mutex will be finally unlocked + m_mutex.Unlock(); +} + +void wxThreadInternal::Pause() +{ + 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 wxThreadInternal::Resume() +{ + 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); } // ----------------------------------------------------------------------------- @@ -290,7 +400,7 @@ wxThread *wxThread::This() bool wxThread::IsMain() { - return (bool)pthread_equal(pthread_self(), gs_pidMain); + return (bool)pthread_equal(pthread_self(), gs_tidMain); } void wxThread::Yield() @@ -311,6 +421,9 @@ void wxThread::Sleep(unsigned long milliseconds) wxThread::wxThread() { + // add this thread to the global list of all threads + gs_allThreads.Add(this); + p_internal = new wxThreadInternal(); } @@ -346,7 +459,7 @@ wxThreadError wxThread::Create() pthread_attr_setschedparam(&attr, &sp); } - // this is the point of no return + // create the new OS thread object int rc = pthread_create(&p_internal->thread_id, &attr, wxThreadInternal::PthreadStart, (void *)this); pthread_attr_destroy(&attr); @@ -441,7 +554,7 @@ wxThreadError wxThread::Resume() if ( p_internal->GetState() == STATE_PAUSED ) { - p_internal->SetState(STATE_RUNNING); + p_internal->Resume(); return wxTHREAD_NO_ERROR; } @@ -506,27 +619,44 @@ wxThreadError wxThread::Kill() void wxThread::Exit(void *status) { - wxThread *ptr = this; - THREAD_SEND_EXIT_MSG(ptr); - + // 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() const { 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() { + // remove this thread from the global array + gs_allThreads.Remove(this); } // ----------------------------------------------------------------------------- @@ -583,7 +713,7 @@ bool wxThreadModule::OnInit() gs_mutexGui = new wxMutex(); wxThreadGuiInit(); - gs_pidMain = (int)getpid(); + gs_tidMain = pthread_self(); gs_mutexGui->Lock(); return TRUE; @@ -591,9 +721,23 @@ bool wxThreadModule::OnInit() 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."); + + for ( size_t n = 0u; n < count; n++ ) + { + gs_allThreads[n]->Delete(); + } + + // destroy GUI mutex gs_mutexGui->Unlock(); wxThreadGuiExit(); delete gs_mutexGui; + // and free TLD slot (void)pthread_key_delete(gs_keySelf); }