pcsx2/common/include/Utilities/Threading.h

434 lines
13 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2010 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <semaphore.h>
#include <errno.h> // EBUSY
#include <pthread.h>
#include "Pcsx2Defs.h"
#include "ScopedPtr.h"
#include "TraceLog.h"
#undef Yield // release the burden of windows.h global namespace spam.
#define AffinityAssert_AllowFrom_MainUI() \
pxAssertMsg( wxThread::IsMain(), "Thread affinity violation: Call allowed from main thread only." )
// --------------------------------------------------------------------------------------
// pxThreadLog / ConsoleLogSource_Threading
// --------------------------------------------------------------------------------------
class ConsoleLogSource_Threading : ConsoleLogSource
{
typedef ConsoleLogSource _parent;
public:
using _parent::IsActive;
ConsoleLogSource_Threading();
bool Write( const wxString& thrname, const wxChar* msg ) {
return _parent::Write( wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg );
}
bool Warn( const wxString& thrname, const wxChar* msg ) {
return _parent::Warn( wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg );
}
bool Error( const wxString& thrname, const wxChar* msg ) {
return _parent::Error( wxsFormat(L"(thread:%s) ", WX_STR(thrname)) + msg );
}
};
extern ConsoleLogSource_Threading pxConLog_Thread;
#define pxThreadLog pxConLog_Thread.IsActive() && pxConLog_Thread
// --------------------------------------------------------------------------------------
// PCSX2_THREAD_LOCAL - Defines platform/operating system support for Thread Local Storage
// --------------------------------------------------------------------------------------
// For complimentary support for TLS, include Utilities/TlsVariable.inl, and use the
// DeclareTls macro in the place of __threadlocal.
//
//#define PCSX2_THREAD_LOCAL 0 // uncomment this line to force-disable native TLS (useful for testing TlsVariable on windows/linux)
#ifndef PCSX2_THREAD_LOCAL
# ifdef __WXMAC__
# define PCSX2_THREAD_LOCAL 0
# else
# define PCSX2_THREAD_LOCAL 1
# endif
#endif
class wxTimeSpan;
namespace Threading
{
class pxThread;
class RwMutex;
extern void pxTestCancel();
extern pxThread* pxGetCurrentThread();
extern wxString pxGetCurrentThreadName();
extern u64 GetThreadCpuTime();
extern u64 GetThreadTicksPerSecond();
// Yields the current thread and provides cancellation points if the thread is managed by
// pxThread. Unmanaged threads use standard Sleep.
extern void pxYield( int ms );
}
namespace Exception
{
class BaseThreadError : public RuntimeError
{
DEFINE_EXCEPTION_COPYTORS( BaseThreadError, RuntimeError )
DEFINE_EXCEPTION_MESSAGES( BaseThreadError )
public:
Threading::pxThread* m_thread;
protected:
BaseThreadError() {
m_thread = NULL;
}
public:
explicit BaseThreadError( Threading::pxThread* _thread )
{
m_thread = _thread;
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
}
explicit BaseThreadError( Threading::pxThread& _thread )
{
m_thread = &_thread;
m_message_diag = L"An unspecified thread-related error occurred (thread=%s)";
}
virtual wxString FormatDiagnosticMessage() const;
virtual wxString FormatDisplayMessage() const;
Threading::pxThread& Thread();
const Threading::pxThread& Thread() const;
};
class ThreadCreationError : public BaseThreadError
{
DEFINE_EXCEPTION_COPYTORS( ThreadCreationError, BaseThreadError )
public:
explicit ThreadCreationError( Threading::pxThread* _thread )
{
m_thread = _thread;
SetBothMsgs( L"Thread creation failure. An unspecified error occurred while trying to create the %s thread." );
}
explicit ThreadCreationError( Threading::pxThread& _thread )
{
m_thread = &_thread;
SetBothMsgs( L"Thread creation failure. An unspecified error occurred while trying to create the %s thread." );
}
};
}
namespace Threading
{
// --------------------------------------------------------------------------------------
// Platform Specific External APIs
// --------------------------------------------------------------------------------------
// The following set of documented functions have Linux/Win32 specific implementations,
// which are found in WinThreads.cpp and LnxThreads.cpp
// Releases a timeslice to other threads.
extern void Timeslice();
// For use in spin/wait loops.
extern void SpinWait();
// Use prior to committing data to another thread (internal memcpy_qwc does not use fencing,
// so that many memcpys can be issued in a row more efficiently)
extern void StoreFence();
// Optional implementation to enable hires thread/process scheduler for the operating system.
// Needed by Windows, but might not be relevant to other platforms.
extern void EnableHiresScheduler();
extern void DisableHiresScheduler();
// sleeps the current thread for the given number of milliseconds.
extern void Sleep( int ms );
// --------------------------------------------------------------------------------------
// AtomicExchange / AtomicIncrement
// --------------------------------------------------------------------------------------
// Our fundamental interlocking functions. All other useful interlocks can be derived
// from these little beasties! (these are all implemented internally using cross-platform
// implementations of _InterlockedExchange and such)
extern u32 AtomicRead( volatile u32& Target );
extern s32 AtomicRead( volatile s32& Target );
extern u32 AtomicExchange( volatile u32& Target, u32 value );
extern s32 AtomicExchange( volatile s32& Target, s32 value );
extern u32 AtomicExchangeAdd( volatile u32& Target, u32 value );
extern s32 AtomicExchangeAdd( volatile s32& Target, s32 value );
extern s32 AtomicExchangeSub( volatile s32& Target, s32 value );
extern u32 AtomicIncrement( volatile u32& Target );
extern s32 AtomicIncrement( volatile s32& Target );
extern u32 AtomicDecrement( volatile u32& Target );
extern s32 AtomicDecrement( volatile s32& Target );
extern bool AtomicBitTestAndReset( volatile u32& bitset, u8 bit );
extern bool AtomicBitTestAndReset( volatile s32& bitset, u8 bit );
extern void* _AtomicExchangePointer( volatile uptr& target, uptr value );
extern void* _AtomicCompareExchangePointer( volatile uptr& target, uptr value, uptr comparand );
#define AtomicExchangePointer( dest, src ) _AtomicExchangePointer( (uptr&)dest, (uptr)src )
#define AtomicCompareExchangePointer( dest, comp, src ) _AtomicExchangePointer( (uptr&)dest, (uptr)comp, (uptr)src )
// pthread Cond is an evil api that is not suited for Pcsx2 needs.
// Let's not use it. Use mutexes and semaphores instead to create waits. (Air)
#if 0
struct WaitEvent
{
pthread_cond_t cond;
pthread_mutex_t mutex;
WaitEvent();
~WaitEvent() throw();
void Set();
void Wait();
};
#endif
// --------------------------------------------------------------------------------------
// NonblockingMutex
// --------------------------------------------------------------------------------------
// This is a very simple non-blocking mutex, which behaves similarly to pthread_mutex's
// trylock(), but without any of the extra overhead needed to set up a structure capable
// of blocking waits. It basically optimizes to a single InterlockedExchange.
//
// Simple use: if TryAcquire() returns false, the Bool is already interlocked by another thread.
// If TryAcquire() returns true, you've locked the object and are *responsible* for unlocking
// it later.
//
class NonblockingMutex
{
protected:
volatile int val;
public:
NonblockingMutex() : val( false ) {}
virtual ~NonblockingMutex() throw() {}
bool TryAcquire() throw()
{
return !AtomicExchange( val, true );
}
bool IsLocked()
{ return !!val; }
void Release()
{
AtomicExchange( val, false );
}
};
class Semaphore
{
protected:
sem_t m_sema;
public:
Semaphore();
virtual ~Semaphore() throw();
void Reset();
void Post();
void Post( int multiple );
void WaitWithoutYield();
bool WaitWithoutYield( const wxTimeSpan& timeout );
void WaitNoCancel();
void WaitNoCancel( const wxTimeSpan& timeout );
int Count();
void Wait();
bool Wait( const wxTimeSpan& timeout );
};
class Mutex
{
protected:
pthread_mutex_t m_mutex;
public:
Mutex();
virtual ~Mutex() throw();
virtual bool IsRecursive() const { return false; }
void Recreate();
bool RecreateIfLocked();
void Detach();
void Acquire();
bool Acquire( const wxTimeSpan& timeout );
bool TryAcquire();
void Release();
void AcquireWithoutYield();
bool AcquireWithoutYield( const wxTimeSpan& timeout );
void Wait();
bool Wait( const wxTimeSpan& timeout );
void WaitWithoutYield();
bool WaitWithoutYield( const wxTimeSpan& timeout );
protected:
// empty constructor used by MutexLockRecursive
Mutex( bool ) {}
};
class MutexRecursive : public Mutex
{
public:
MutexRecursive();
virtual ~MutexRecursive() throw();
virtual bool IsRecursive() const { return true; }
};
// --------------------------------------------------------------------------------------
// ScopedLock
// --------------------------------------------------------------------------------------
// Helper class for using Mutexes. Using this class provides an exception-safe (and
// generally clean) method of locking code inside a function or conditional block. The lock
// will be automatically released on any return or exit from the function.
//
// Const qualification note:
// ScopedLock takes const instances of the mutex, even though the mutex is modified
// by locking and unlocking. Two rationales:
//
// 1) when designing classes with accessors (GetString, GetValue, etc) that need mutexes,
// this class needs a const hack to allow those accessors to be const (which is typically
// *very* important).
//
// 2) The state of the Mutex is guaranteed to be unchanged when the calling function or
// scope exits, by any means. Only via manual calls to Release or Acquire does that
// change, and typically those are only used in very special circumstances of their own.
//
class ScopedLock
{
DeclareNoncopyableObject(ScopedLock);
protected:
Mutex* m_lock;
bool m_IsLocked;
public:
virtual ~ScopedLock() throw();
explicit ScopedLock( const Mutex* locker=NULL );
explicit ScopedLock( const Mutex& locker );
void AssignAndLock( const Mutex& locker );
void AssignAndLock( const Mutex* locker );
void Assign( const Mutex& locker );
void Assign( const Mutex* locker );
void Release();
void Acquire();
bool IsLocked() const { return m_IsLocked; }
protected:
// Special constructor used by ScopedTryLock
ScopedLock( const Mutex& locker, bool isTryLock );
};
class ScopedTryLock : public ScopedLock
{
public:
ScopedTryLock( const Mutex& locker ) : ScopedLock( locker, true ) { }
virtual ~ScopedTryLock() throw() {}
bool Failed() const { return !m_IsLocked; }
};
// --------------------------------------------------------------------------------------
// ScopedNonblockingLock
// --------------------------------------------------------------------------------------
// A ScopedTryLock branded for use with Nonblocking mutexes. See ScopedTryLock for details.
//
class ScopedNonblockingLock
{
DeclareNoncopyableObject(ScopedNonblockingLock);
protected:
NonblockingMutex& m_lock;
bool m_IsLocked;
public:
ScopedNonblockingLock( NonblockingMutex& locker ) :
m_lock( locker )
, m_IsLocked( m_lock.TryAcquire() )
{
}
virtual ~ScopedNonblockingLock() throw()
{
if( m_IsLocked )
m_lock.Release();
}
bool Failed() const { return !m_IsLocked; }
};
// --------------------------------------------------------------------------------------
// ScopedLockBool
// --------------------------------------------------------------------------------------
// A ScopedLock in which you specify an external bool to get updated on locks/unlocks.
// Note that the isLockedBool should only be used as an indicator for the locked status,
// and not actually depended on for thread synchronization...
struct ScopedLockBool {
ScopedLock m_lock;
volatile __aligned(4) bool& m_bool;
#ifdef __linux__
ScopedLockBool(Mutex& mutexToLock, volatile bool& isLockedBool)
#else
ScopedLockBool(Mutex& mutexToLock, volatile __aligned(4) bool& isLockedBool)
#endif
: m_lock(mutexToLock),
m_bool(isLockedBool) {
m_bool = m_lock.IsLocked();
}
virtual ~ScopedLockBool() throw() {
m_bool = false;
}
void Acquire() {
m_lock.Acquire();
m_bool = m_lock.IsLocked();
}
void Release() {
m_bool = false;
m_lock.Release();
}
};
}