more threds fixes, more threads tests - seems to work ok for non GUI case

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@4798 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
1999-12-02 17:59:35 +00:00
parent 48c8a76b81
commit 4c460b340f
2 changed files with 288 additions and 87 deletions

View File

@@ -32,8 +32,8 @@
//#define TEST_ARRAYS
//#define TEST_LOG
//#define TEST_STRINGS
//#define TEST_THREADS
#define TEST_TIME
#define TEST_THREADS
//#define TEST_TIME
//#define TEST_LONGLONG
// ============================================================================
@@ -392,6 +392,8 @@ void TestThreadSuspend()
thread->Resume();
}
puts("Waiting until it terminates now");
// wait until the thread terminates
gs_cond.Wait();
@@ -407,6 +409,12 @@ void TestThreadDelete()
puts("\n*** Testing thread delete function ***");
MyDetachedThread *thread0 = new MyDetachedThread(30, 'W');
thread0->Delete();
puts("\nDeleted a thread which didn't start to run yet.");
MyDetachedThread *thread1 = new MyDetachedThread(30, 'Y');
thread1->Run();
@@ -429,19 +437,19 @@ void TestThreadDelete()
puts("\nDeleted a sleeping thread.");
MyJoinableThread *thread3 = new MyJoinableThread(20);
thread3->Run();
MyJoinableThread thread3(20);
thread3.Run();
thread3->Delete();
thread3.Delete();
puts("\nDeleted a joinable thread.");
MyJoinableThread *thread4 = new MyJoinableThread(2);
thread4->Run();
MyJoinableThread thread4(2);
thread4.Run();
wxThread::Sleep(300);
thread4->Delete();
thread4.Delete();
puts("\nDeleted a joinable thread which already terminated.");
@@ -598,11 +606,11 @@ int main(int argc, char **argv)
if ( argc > 1 && argv[1][0] == 't' )
wxLog::AddTraceMask("thread");
if ( 0 )
if ( 1 )
TestDetachedThreads();
if ( 0 )
if ( 1 )
TestJoinableThreads();
if ( 0 )
if ( 1 )
TestThreadSuspend();
if ( 1 )
TestThreadDelete();

View File

@@ -224,9 +224,12 @@ wxMutexError wxMutex::Unlock()
// wxCondition (Posix implementation)
//--------------------------------------------------------------------
// notice that we must use a mutex with POSIX condition variables to ensure
// that the worker thread doesn't signal condition before the waiting thread
// starts to wait for it
// The native POSIX condition variables are dumb: if the condition is signaled
// before another thread starts to wait on it, the signal is lost and so this
// other thread will be never woken up. It's much more convenient to us to
// remember that the condition was signaled and to return from Wait()
// immediately in this case (this is more like Win32 automatic event objects)
class wxConditionInternal
{
public:
@@ -239,20 +242,34 @@ public:
void Signal();
void Broadcast();
void WaitDone();
bool ShouldWait();
bool HasWaiters();
private:
pthread_mutex_t m_mutex;
pthread_cond_t m_condition;
bool m_wasSignaled; // TRUE if condition was signaled while
// nobody waited for it
size_t m_nWaiters; // TRUE if someone already waits for us
pthread_mutex_t m_mutexProtect; // protects access to vars above
pthread_mutex_t m_mutex; // the mutex used with the condition
pthread_cond_t m_condition; // the condition itself
};
wxConditionInternal::wxConditionInternal()
{
m_wasSignaled = FALSE;
m_nWaiters = 0;
if ( pthread_cond_init(&m_condition, (pthread_condattr_t *)NULL) != 0 )
{
// this is supposed to never happen
wxFAIL_MSG( _T("pthread_cond_init() failed") );
}
if ( pthread_mutex_init(&m_mutex, (pthread_mutexattr_t*)NULL) != 0 )
if ( pthread_mutex_init(&m_mutex, (pthread_mutexattr_t *)NULL) != 0 ||
pthread_mutex_init(&m_mutexProtect, NULL) != 0 )
{
// neither this
wxFAIL_MSG( _T("wxCondition: pthread_mutex_init() failed") );
@@ -279,60 +296,131 @@ wxConditionInternal::~wxConditionInternal()
wxLogDebug(_T("wxCondition: failed to unlock the mutex"));
}
if ( pthread_mutex_destroy( &m_mutex ) != 0 )
if ( pthread_mutex_destroy( &m_mutex ) != 0 ||
pthread_mutex_destroy( &m_mutexProtect ) != 0 )
{
wxLogDebug(_T("Failed to destroy mutex (it is probably locked)"));
}
}
void wxConditionInternal::WaitDone()
{
MutexLock lock(m_mutexProtect);
m_wasSignaled = FALSE;
m_nWaiters--;
}
bool wxConditionInternal::ShouldWait()
{
MutexLock lock(m_mutexProtect);
if ( m_wasSignaled )
{
// the condition was signaled before we started to wait, reset the
// flag and return
m_wasSignaled = FALSE;
return FALSE;
}
// we start to wait for it
m_nWaiters++;
return TRUE;
}
bool wxConditionInternal::HasWaiters()
{
MutexLock lock(m_mutexProtect);
if ( m_nWaiters )
{
// someone waits for us, signal the condition normally
return TRUE;
}
// nobody waits for us and may be never will - so just remember that the
// condition was signaled and don't do anything else
m_wasSignaled = TRUE;
return FALSE;
}
void wxConditionInternal::Wait()
{
if ( pthread_cond_wait( &m_condition, &m_mutex ) != 0 )
if ( ShouldWait() )
{
// not supposed to ever happen
wxFAIL_MSG( _T("pthread_cond_wait() failed") );
if ( pthread_cond_wait( &m_condition, &m_mutex ) != 0 )
{
// not supposed to ever happen
wxFAIL_MSG( _T("pthread_cond_wait() failed") );
}
}
WaitDone();
}
bool wxConditionInternal::WaitWithTimeout(const timespec* ts)
{
switch ( pthread_cond_timedwait( &m_condition, &m_mutex, ts ) )
bool ok;
if ( ShouldWait() )
{
case 0:
// condition signaled
return TRUE;
switch ( pthread_cond_timedwait( &m_condition, &m_mutex, ts ) )
{
case 0:
// condition signaled
ok = TRUE;
break;
default:
wxLogDebug(_T("pthread_cond_timedwait() failed"));
default:
wxLogDebug(_T("pthread_cond_timedwait() failed"));
// fall through
// fall through
case ETIMEDOUT:
case EINTR:
// wait interrupted or timeout elapsed
return FALSE;
case ETIMEDOUT:
case EINTR:
// wait interrupted or timeout elapsed
ok = FALSE;
}
}
else
{
// the condition had already been signaled before
ok = TRUE;
}
WaitDone();
return ok;
}
void wxConditionInternal::Signal()
{
MutexLock lock(m_mutex);
if ( pthread_cond_signal( &m_condition ) != 0 )
if ( HasWaiters() )
{
// shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_signal() failed"));
MutexLock lock(m_mutex);
if ( pthread_cond_signal( &m_condition ) != 0 )
{
// shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_signal() failed"));
}
}
}
void wxConditionInternal::Broadcast()
{
MutexLock lock(m_mutex);
if ( pthread_cond_broadcast( &m_condition ) != 0 )
if ( HasWaiters() )
{
// shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_broadcast() failed"));
MutexLock lock(m_mutex);
if ( pthread_cond_broadcast( &m_condition ) != 0 )
{
// shouldn't ever happen
wxFAIL_MSG(_T("pthread_cond_broadcast() failed"));
}
}
}
@@ -396,6 +484,8 @@ public:
void Wait();
// wake up threads waiting for our termination
void SignalExit();
// wake up threads waiting for our start
void SignalRun() { m_condRun.Signal(); }
// go to sleep until Resume() is called
void Pause();
// resume the thread
@@ -418,8 +508,16 @@ public:
void SetExitCode(wxThread::ExitCode exitcode) { m_exitcode = exitcode; }
wxThread::ExitCode GetExitCode() const { return m_exitcode; }
// the pause flag
void SetReallyPaused(bool paused) { m_isPaused = paused; }
bool IsReallyPaused() const { return m_isPaused; }
// tell the thread that it is a detached one
void Detach() { m_shouldBeJoined = m_shouldBroadcast = FALSE; }
void Detach()
{
m_shouldBeJoined = m_shouldBroadcast = FALSE;
m_isDetached = TRUE;
}
// but even detached threads need to notifyus about their termination
// sometimes - tell the thread that it should do it
void Notify() { m_shouldBroadcast = TRUE; }
@@ -432,6 +530,9 @@ private:
// this flag is set when the thread should terminate
bool m_cancelled;
// this flag is set when the thread is blocking on m_condSuspend
bool m_isPaused;
// the thread exit code - only used for joinable (!detached) threads and
// is only valid after the thread termination
wxThread::ExitCode m_exitcode;
@@ -441,6 +542,7 @@ private:
wxCriticalSection m_csJoinFlag;
bool m_shouldBeJoined;
bool m_shouldBroadcast;
bool m_isDetached;
// VZ: it's possible that we might do with less than three different
// condition objects - for example, m_condRun and m_condEnd a priori
@@ -479,6 +581,10 @@ void *wxThreadInternal::PthreadStart(void *ptr)
return (void *)-1;
}
// have to declare this before pthread_cleanup_push() which defines a
// block!
bool dontRunAtAll;
#if HAVE_THREAD_CLEANUP_FUNCTIONS
// install the cleanup handler which will be called if the thread is
// cancelled
@@ -488,22 +594,34 @@ void *wxThreadInternal::PthreadStart(void *ptr)
// wait for the condition to be signaled from Run()
pthread->m_condRun.Wait();
// call the main entry
pthread->m_exitcode = thread->Entry();
wxLogTrace(TRACE_THREADS, _T("Thread %ld left its Entry()."),
pthread->GetId());
// test whether we should run the run at all - may be it was deleted
// before it started to Run()?
{
wxCriticalSectionLocker lock(thread->m_critsect);
wxLogTrace(TRACE_THREADS, _T("Thread %ld changes state to EXITED."),
dontRunAtAll = pthread->GetState() == STATE_NEW &&
pthread->WasCancelled();
}
if ( !dontRunAtAll )
{
// call the main entry
pthread->m_exitcode = thread->Entry();
wxLogTrace(TRACE_THREADS, _T("Thread %ld left its Entry()."),
pthread->GetId());
// change the state of the thread to "exited" so that PthreadCleanup
// handler won't do anything from now (if it's called before we do
// pthread_cleanup_pop below)
pthread->SetState(STATE_EXITED);
{
wxCriticalSectionLocker lock(thread->m_critsect);
wxLogTrace(TRACE_THREADS, _T("Thread %ld changes state to EXITED."),
pthread->GetId());
// change the state of the thread to "exited" so that
// PthreadCleanup handler won't do anything from now (if it's
// called before we do pthread_cleanup_pop below)
pthread->SetState(STATE_EXITED);
}
}
// NB: at least under Linux, pthread_cleanup_push/pop are macros and pop
@@ -514,12 +632,21 @@ void *wxThreadInternal::PthreadStart(void *ptr)
pthread_cleanup_pop(FALSE);
#endif // HAVE_THREAD_CLEANUP_FUNCTIONS
// terminate the thread
thread->Exit(pthread->m_exitcode);
if ( dontRunAtAll )
{
delete thread;
wxFAIL_MSG(wxT("wxThread::Exit() can't return."));
return EXITCODE_CANCELLED;
}
else
{
// terminate the thread
thread->Exit(pthread->m_exitcode);
return NULL;
wxFAIL_MSG(wxT("wxThread::Exit() can't return."));
return NULL;
}
}
#if HAVE_THREAD_CLEANUP_FUNCTIONS
@@ -556,9 +683,13 @@ wxThreadInternal::wxThreadInternal()
m_threadId = 0;
m_exitcode = 0;
// set to TRUE only when the thread starts waiting on m_condSuspend
m_isPaused = FALSE;
// defaults for joinable threads
m_shouldBeJoined = TRUE;
m_shouldBroadcast = TRUE;
m_isDetached = FALSE;
}
wxThreadInternal::~wxThreadInternal()
@@ -570,7 +701,7 @@ wxThreadError wxThreadInternal::Run()
wxCHECK_MSG( GetState() == STATE_NEW, wxTHREAD_RUNNING,
wxT("thread may only be started once after Create()") );
m_condRun.Signal();
SignalRun();
SetState(STATE_RUNNING);
@@ -584,29 +715,39 @@ void wxThreadInternal::Wait()
if ( wxThread::IsMain() )
wxMutexGuiLeave();
bool isDetached = m_isDetached;
long id = GetId();
wxLogTrace(TRACE_THREADS, _T("Starting to wait for thread %ld to exit."),
GetId());
id);
// wait until the thread terminates (we're blocking in _another_ thread,
// of course)
m_condEnd.Wait();
// to avoid memory leaks we should call pthread_join(), but it must only
// be done once
wxCriticalSectionLocker lock(m_csJoinFlag);
wxLogTrace(TRACE_THREADS, _T("Finished waiting for thread %ld."), id);
if ( m_shouldBeJoined )
// we can't use any member variables any more if the thread is detached
// because it could be already deleted
if ( !isDetached )
{
// FIXME shouldn't we set cancellation type to DISABLED here? If we're
// cancelled inside pthread_join(), things will almost certainly
// break - but if we disable the cancellation, we might deadlock
if ( pthread_join(GetId(), &m_exitcode) != 0 )
{
wxLogError(_T("Failed to join a thread, potential memory leak "
"detected - please restart the program"));
}
// to avoid memory leaks we should call pthread_join(), but it must
// only be done once
wxCriticalSectionLocker lock(m_csJoinFlag);
m_shouldBeJoined = FALSE;
if ( m_shouldBeJoined )
{
// FIXME shouldn't we set cancellation type to DISABLED here? If
// we're cancelled inside pthread_join(), things will almost
// certainly break - but if we disable the cancellation, we
// might deadlock
if ( pthread_join(id, &m_exitcode) != 0 )
{
wxLogError(_("Failed to join a thread, potential memory leak "
"detected - please restart the program"));
}
m_shouldBeJoined = FALSE;
}
}
// reacquire GUI mutex
@@ -648,10 +789,23 @@ void wxThreadInternal::Resume()
wxCHECK_RET( m_state == STATE_PAUSED,
wxT("can't resume thread which is not suspended.") );
wxLogTrace(TRACE_THREADS, _T("Waking up thread %ld"), GetId());
// the thread might be not actually paused yet - if there were no call to
// TestDestroy() since the last call to Pause() for example
if ( IsReallyPaused() )
{
wxLogTrace(TRACE_THREADS, _T("Waking up thread %ld"), GetId());
// wake up Pause()
m_condSuspend.Signal();
// wake up Pause()
m_condSuspend.Signal();
// reset the flag
SetReallyPaused(FALSE);
}
else
{
wxLogTrace(TRACE_THREADS, _T("Thread %ld is not yet really paused"),
GetId());
}
SetState(STATE_RUNNING);
}
@@ -866,6 +1020,9 @@ unsigned long wxThread::GetId() const
wxThreadError wxThread::Pause()
{
wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't pause itself") );
wxCriticalSectionLocker lock(m_critsect);
if ( m_internal->GetState() != STATE_RUNNING )
@@ -875,6 +1032,9 @@ wxThreadError wxThread::Pause()
return wxTHREAD_NOT_RUNNING;
}
wxLogTrace(TRACE_THREADS, _T("Asking thread %ld to pause."),
GetId());
// just set a flag, the thread will be really paused only during the next
// call to TestDestroy()
m_internal->SetState(STATE_PAUSED);
@@ -884,20 +1044,17 @@ wxThreadError wxThread::Pause()
wxThreadError wxThread::Resume()
{
m_critsect.Enter();
wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't resume itself") );
wxCriticalSectionLocker lock(m_critsect);
wxThreadState state = m_internal->GetState();
// the thread might be not actually paused yet - if there were no call to
// TestDestroy() since the last call to Pause(), so avoid that
// TestDestroy() deadlocks trying to enter m_critsect by leaving it before
// calling Resume()
m_critsect.Leave();
switch ( state )
{
case STATE_PAUSED:
wxLogTrace(TRACE_THREADS, _T("Thread %ld is suspended, resuming."),
wxLogTrace(TRACE_THREADS, _T("Thread %ld suspended, resuming."),
GetId());
m_internal->Resume();
@@ -935,6 +1092,9 @@ wxThread::ExitCode wxThread::Wait()
wxThreadError wxThread::Delete(ExitCode *rc)
{
wxCHECK_MSG( This() != this, wxTHREAD_MISC_ERROR,
_T("a thread can't delete itself") );
m_critsect.Enter();
wxThreadState state = m_internal->GetState();
@@ -954,6 +1114,12 @@ wxThreadError wxThread::Delete(ExitCode *rc)
switch ( state )
{
case STATE_NEW:
// we need to wake up the thread so that PthreadStart() will
// terminate - right now it's blocking on m_condRun
m_internal->SignalRun();
// fall through
case STATE_EXITED:
// nothing to do
break;
@@ -1032,6 +1198,10 @@ wxThreadError wxThread::Kill()
void wxThread::Exit(ExitCode status)
{
wxASSERT_MSG( This() == this,
_T("wxThread::Exit() can only be called in the "
"context of the same thread") );
// from the moment we call OnExit(), the main program may terminate at any
// moment, so mark this thread as being already in process of being
// deleted or wxThreadModule::OnExit() will try to delete it again
@@ -1076,10 +1246,16 @@ void wxThread::Exit(ExitCode status)
// also test whether we were paused
bool wxThread::TestDestroy()
{
wxASSERT_MSG( This() == this,
_T("wxThread::TestDestroy() can only be called in the "
"context of the same thread") );
m_critsect.Enter();
if ( m_internal->GetState() == STATE_PAUSED )
{
m_internal->SetReallyPaused(TRUE);
// 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
@@ -1105,8 +1281,8 @@ wxThread::~wxThread()
if ( m_internal->GetState() != STATE_EXITED &&
m_internal->GetState() != STATE_NEW )
{
wxLogDebug(_T("The thread is being destroyed although it is still "
"running! The application may crash."));
wxLogDebug(_T("The thread %ld is being destroyed although it is still "
"running! The application may crash."), GetId());
}
m_critsect.Leave();
@@ -1116,6 +1292,17 @@ wxThread::~wxThread()
// remove this thread from the global array
gs_allThreads.Remove(this);
// detached thread will decrement this counter in DeleteThread(), but it
// is not called for the joinable threads, so do it here
if ( !m_isDetached )
{
MutexLock lock(gs_mutexDeleteThread);
gs_nThreadsBeingDeleted--;
wxLogTrace(TRACE_THREADS, _T("%u scheduled for deletion threads left."),
gs_nThreadsBeingDeleted - 1);
}
}
// -----------------------------------------------------------------------------
@@ -1212,7 +1399,10 @@ void wxThreadModule::OnExit()
// terminate any threads left
size_t count = gs_allThreads.GetCount();
if ( count != 0u )
wxLogDebug(wxT("Some threads were not terminated by the application."));
{
wxLogDebug(wxT("%u threads were not terminated by the application."),
count);
}
for ( size_t n = 0u; n < count; n++ )
{
@@ -1268,6 +1458,9 @@ static void DeleteThread(wxThread *This)
"one?") );
}
wxLogTrace(TRACE_THREADS, _T("%u scheduled for deletion threads left."),
gs_nThreadsBeingDeleted - 1);
if ( !--gs_nThreadsBeingDeleted )
{
// no more threads left, signal it