/* Copyright (C) 2002 The gtkmm Development Team * * This library 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 Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ _MODULE(GLib-2.0) _CONFIGINCLUDE(glibmmconfig.h) #include #include #include #include namespace Glib { namespace Threads { /** @defgroup Threads Threads * Thread abstraction; including threads, different mutexes, * conditions and thread private data. * @{ */ enum NotLock { NOT_LOCK }; enum TryLock { TRY_LOCK }; class Mutex; class RecMutex; class RWLock; /** Exception class for thread-related errors. */ _WRAP_GERROR(ThreadError, GThreadError) /** Represents a running thread. * An instance of this class can only be obtained with create(), self(), * or wrap(GThread*). It's not possible to delete a Thread object. * You must call join() to avoid a memory leak. * * @note g_thread_exit() is not wrapped, because that function exits a thread * without any cleanup. That's especially dangerous in C++ code, since the * destructors of automatic objects won't be invoked. Instead, you can throw * a Threads::Thread::Exit exception, which will be caught by the internal thread * entry function. * * @note You might have noticed that the thread entry slot doesn't have the * usual void* return value. If you want to return any data from your thread * you can pass an additional output argument to the thread's entry slot. */ class Thread { public: class Exit; //See http://bugzilla.gnome.org/show_bug.cgi?id=512348 about the sigc::trackable issue. /** Creates a new thread. * You can wait for this thread's termination by calling join(). * * The new thread executes the function or method @a slot points to. You can * pass additional arguments using sigc::bind(). If the thread was created * successfully, it is returned, otherwise a Threads::ThreadError exception is thrown. * * Because sigc::trackable is not thread safe, if the slot represents a * non-static class method (that is, it is created by sigc::mem_fun()), the * class concerned should not derive from sigc::trackable. * * @param slot A slot to execute in the new thread. * @return The new Thread* on success. * @throw Glib::Threads::ThreadError */ static Thread* create(const sigc::slot& slot); /** Returns the Thread* corresponding to the calling thread. * @return The current thread. */ static Thread* self(); /** Waits until the thread finishes. * Waits until the thread finishes, i.e. the slot, as given to create(), * returns or g_thread_exit() is called by the thread. (Calling * g_thread_exit() in a C++ program should be avoided.) All resources of * the thread including the Glib::Threads::Thread object are released. */ void join(); /** Gives way to other threads waiting to be scheduled. * This function is often used as a method to make busy wait less evil. But * in most cases, you will encounter, there are better methods to do that. * So in general you shouldn't use this function. */ static void yield(); GThread* gobj() { return &gobject_; } const GThread* gobj() const { return &gobject_; } private: GThread gobject_; // Glib::Thread can neither be constructed nor deleted. Thread(); void operator delete(void*, size_t); // noncopyable Thread(const Thread&); Thread& operator=(const Thread&); }; /** %Exception class used to exit from a thread. * @code * throw Glib::Threads::Thread::Exit(); * @endcode * Write this if you want to exit from a thread created by Threads::Thread::create(). * Of course you must make sure not to catch Threads::Thread::Exit by accident, i.e. * when using catch(...) somewhere in your code. */ class Thread::Exit {}; /** @relates Glib::Threads::Thread */ Thread* wrap(GThread* gobject); /** Represents a mutex (mutual exclusion). * It can be used to protect data against shared access. Try to use * Mutex::Lock instead of calling lock() and unlock() directly -- * it will make your life much easier. * * @note Glib::Mutex is not recursive, i.e. a thread will deadlock, if it * already has locked the mutex while calling lock(). Use Glib::RecMutex * instead, if you need recursive mutexes. */ class Mutex { public: class Lock; Mutex(); ~Mutex(); /** Locks the mutex. * If mutex is already locked by another thread, the current thread will * block until mutex is unlocked by the other thread. * @see Mutex::Lock */ void lock(); /** Tries to lock the mutex. * If the mutex is already locked by another thread, it immediately returns * @c false. Otherwise it locks the mutex and returns @c true. * @return Whether the mutex could be locked. * @see Mutex::Lock */ bool trylock(); /** Unlocks the mutex. * If another thread is blocked in a lock() call for this mutex, it will be * woken and can lock the mutex itself. * @see Mutex::Lock */ void unlock(); GMutex* gobj() { return &gobject_; } private: GMutex gobject_; // noncopyable Mutex(const Mutex&); Mutex& operator=(const Mutex&); }; /** Utility class for exception-safe mutex locking. * @par Usage example: * @code * { * Glib::Mutex::Lock lock (mutex); // calls mutex.lock() * do_something(); * } // the destructor calls mutex.unlock() * @endcode * As you can see, the compiler takes care of the unlocking. This is not * only exception safe but also much less error-prone. You could even * return while still holding the lock and it will be released * properly. */ class Mutex::Lock { public: explicit inline Lock(Mutex& mutex); inline Lock(Mutex& mutex, NotLock); inline Lock(Mutex& mutex, TryLock); inline ~Lock(); inline void acquire(); inline bool try_acquire(); inline void release(); inline bool locked() const; private: Mutex& mutex_; bool locked_; // noncopyable Lock(const Mutex::Lock&); Mutex::Lock& operator=(const Mutex::Lock&); }; //TODO: Docuemenation class RecMutex { public: class Lock; RecMutex(); ~RecMutex(); void lock(); bool trylock(); void unlock(); GRecMutex* gobj() { return &gobject_; } private: // noncopyable RecMutex(const RecMutex&); RecMutex& operator=(const RecMutex&); #ifndef DOXYGEN_SHOULD_SKIP_THIS // Must be public to allow initialization at compile time. GRecMutex gobject_; #endif }; /** Utility class for exception-safe locking of recursive mutexes. */ class RecMutex::Lock { public: explicit inline Lock(RecMutex& mutex); inline Lock(RecMutex& mutex, NotLock); inline Lock(RecMutex& mutex, TryLock); inline ~Lock(); inline void acquire(); inline bool try_acquire(); inline void release(); inline bool locked() const; private: RecMutex& mutex_; bool locked_; // noncopyable Lock(const RecMutex::Lock&); RecMutex::Lock& operator=(const RecMutex::Lock&); }; //TODO: Documentation class RWLock { public: class ReaderLock; class WriterLock; RWLock(); ~RWLock(); void reader_lock(); bool reader_trylock(); void reader_unlock(); void writer_lock(); bool writer_trylock(); void writer_unlock(); GRWLock* gobj() { return &gobject_; } private: // noncopyable RWLock(const RWLock&); RWLock& operator=(const RWLock&); #ifndef DOXYGEN_SHOULD_SKIP_THIS // Must be public to allow initialization at compile time. GRWLock gobject_; #endif }; /** Utility class for exception-safe locking of read/write locks. */ class RWLock::ReaderLock { public: explicit inline ReaderLock(RWLock& rwlock); inline ReaderLock(RWLock& rwlock, NotLock); inline ReaderLock(RWLock& rwlock, TryLock); inline ~ReaderLock(); inline void acquire(); inline bool try_acquire(); inline void release(); inline bool locked() const; private: RWLock& rwlock_; bool locked_; // noncopyable ReaderLock(const RWLock::ReaderLock&); RWLock::ReaderLock& operator=(const RWLock::ReaderLock&); }; /** Utility class for exception-safe locking of read/write locks. */ class RWLock::WriterLock { public: explicit inline WriterLock(RWLock& rwlock); inline WriterLock(RWLock& rwlock, NotLock); inline WriterLock(RWLock& rwlock, TryLock); inline ~WriterLock(); inline void acquire(); inline bool try_acquire(); inline void release(); inline bool locked() const; private: RWLock& rwlock_; bool locked_; // noncopyable WriterLock(const RWLock::WriterLock&); RWLock::WriterLock& operator=(const RWLock::WriterLock&); }; /** An opaque data structure to represent a condition. * A @a Cond is an object that threads can block on, if they find a certain * condition to be false. If other threads change the state of this condition * they can signal the @a Cond, such that the waiting thread is woken up. * @par Usage example: * @code * Glib::Cond data_cond; * Glib::Mutex data_mutex; * void* current_data = 0; * * void push_data(void* data) * { * Glib::Mutex::Lock lock (data_mutex); * * current_data = data; * data_cond.signal(); * } * * void* pop_data() * { * Glib::Mutex::Lock lock (data_mutex); * * while (!current_data) * data_cond.wait(data_mutex); * * void *const data = current_data; * current_data = 0; * * return data; * } * @endcode */ class Cond { public: Cond(); ~Cond(); /** If threads are waiting for this @a Cond, exactly one of them is woken up. * It is good practice to hold the same lock as the waiting thread, while calling * this method, though not required. * */ void signal(); /** If threads are waiting for this @a Cond, all of them are woken up. * It is good practice to hold the same lock as the waiting thread, while calling * this method, though not required. */ void broadcast(); /** Waits until this thread is woken up on this @a Cond. * The mutex is unlocked before falling asleep and locked again before resuming. * * @param mutex a @a Mutex that is currently locked. * * @note It is important to use the @a wait() and @a wait_until() methods * only inside a loop, which checks for the condition to be true as it is not * guaranteed that the waiting thread will find it fulfilled, even if the signaling * thread left the condition in that state. This is because another thread can have * altered the condition, before the waiting thread got the chance to be woken up, * even if the condition itself is protected by a @a Mutex. */ void wait(Mutex& mutex); /** Waits until this thread is woken up on this @a Cond, but not longer than until the time, that is specified by @a abs_time. * The mutex is unlocked before falling asleep and locked again before resuming. * * @param mutex a @a Mutex that is currently locked. * @param abs_time a max time to wait. * * @note It is important to use the @a wait() and @a wait_until() methods * only inside a loop, which checks for the condition to be true as it is not * guaranteed that the waiting thread will find it fulfilled, even if the signaling * thread left the condition in that state. This is because another thread can have * altered the condition, before the waiting thread got the chance to be woken up, * even if the condition itself is protected by a @a Mutex. */ bool wait_until(Mutex& mutex, gint64 end_time); GCond* gobj() { return &gobject_; } private: GCond gobject_; // noncopyable Cond(const Cond&); Cond& operator=(const Cond&); }; /** Thread-local data pointer. * * It is recommended that all instances of this class are statically allocated. * The first time an instance is used (get(), set() or replace() is called) * gtk+ allocates a scarce OS resource that cannot be deallocated. */ template class Private { public: typedef void (*DestructorFunc) (void*); /** Deletes static_cast(data) */ static void delete_ptr(void* data); /** Constructor. * * @param destructor_func Function pointer, or 0. If @a destructor_func is not 0 * and the stored data pointer is not 0, this function is called when replace() * is called and when the thread exits. */ explicit inline Private(DestructorFunc destructor_func = &Private::delete_ptr); /** Gets the pointer stored in the calling thread. * * @return If no value has yet been set in this thread, 0 is returned. */ inline T* get(); /** Sets the pointer in the calling thread without calling destructor_func(). */ inline void set(T* data); /** Sets the pointer in the calling thread and calls destructor_func(). * If a function pointer (and not 0) was specified in the constructor, and * the stored data pointer before the call to replace() is not 0, then * destructor_func() is called with this old pointer value. * * @newin{2,32} */ inline void replace(T* data); GPrivate* gobj() { return gobject_; } private: GPrivate gobject_; // noncopyable Private(const Private&); Private& operator=(const Private&); }; /** @} group Threads */ /*! A glibmm thread example. * @example thread/thread.cc */ #ifndef DOXYGEN_SHOULD_SKIP_THIS /***************************************************************************/ /* inline implementation */ /***************************************************************************/ /**** Glib::Mutex::Lock ****************************************************/ inline Mutex::Lock::Lock(Mutex& mutex) : mutex_ (mutex), locked_ (true) { mutex_.lock(); } inline Mutex::Lock::Lock(Mutex& mutex, NotLock) : mutex_ (mutex), locked_ (false) {} inline Mutex::Lock::Lock(Mutex& mutex, TryLock) : mutex_ (mutex), locked_ (mutex.trylock()) {} inline Mutex::Lock::~Lock() { if(locked_) mutex_.unlock(); } inline void Mutex::Lock::acquire() { mutex_.lock(); locked_ = true; } inline bool Mutex::Lock::try_acquire() { locked_ = mutex_.trylock(); return locked_; } inline void Mutex::Lock::release() { mutex_.unlock(); locked_ = false; } inline bool Mutex::Lock::locked() const { return locked_; } /**** Glib::RecMutex::Lock *************************************************/ inline RecMutex::Lock::Lock(RecMutex& mutex) : mutex_ (mutex), locked_ (true) { mutex_.lock(); } inline RecMutex::Lock::Lock(RecMutex& mutex, NotLock) : mutex_ (mutex), locked_ (false) {} inline RecMutex::Lock::Lock(RecMutex& mutex, TryLock) : mutex_ (mutex), locked_ (mutex.trylock()) {} inline RecMutex::Lock::~Lock() { if(locked_) mutex_.unlock(); } inline void RecMutex::Lock::acquire() { mutex_.lock(); locked_ = true; } inline bool RecMutex::Lock::try_acquire() { locked_ = mutex_.trylock(); return locked_; } inline void RecMutex::Lock::release() { mutex_.unlock(); locked_ = false; } inline bool RecMutex::Lock::locked() const { return locked_; } /**** Glib::RWLock::ReaderLock *********************************************/ inline RWLock::ReaderLock::ReaderLock(RWLock& rwlock) : rwlock_ (rwlock), locked_ (true) { rwlock_.reader_lock(); } inline RWLock::ReaderLock::ReaderLock(RWLock& rwlock, NotLock) : rwlock_ (rwlock), locked_ (false) {} inline RWLock::ReaderLock::ReaderLock(RWLock& rwlock, TryLock) : rwlock_ (rwlock), locked_ (rwlock.reader_trylock()) {} inline RWLock::ReaderLock::~ReaderLock() { if(locked_) rwlock_.reader_unlock(); } inline void RWLock::ReaderLock::acquire() { rwlock_.reader_lock(); locked_ = true; } inline bool RWLock::ReaderLock::try_acquire() { locked_ = rwlock_.reader_trylock(); return locked_; } inline void RWLock::ReaderLock::release() { rwlock_.reader_unlock(); locked_ = false; } inline bool RWLock::ReaderLock::locked() const { return locked_; } /**** Glib::RWLock::WriterLock *********************************************/ inline RWLock::WriterLock::WriterLock(RWLock& rwlock) : rwlock_ (rwlock), locked_ (true) { rwlock_.writer_lock(); } inline RWLock::WriterLock::WriterLock(RWLock& rwlock, NotLock) : rwlock_ (rwlock), locked_ (false) {} inline RWLock::WriterLock::WriterLock(RWLock& rwlock, TryLock) : rwlock_ (rwlock), locked_ (rwlock.writer_trylock()) {} inline RWLock::WriterLock::~WriterLock() { if(locked_) rwlock_.writer_unlock(); } inline void RWLock::WriterLock::acquire() { rwlock_.writer_lock(); locked_ = true; } inline bool RWLock::WriterLock::try_acquire() { locked_ = rwlock_.writer_trylock(); return locked_; } inline void RWLock::WriterLock::release() { rwlock_.writer_unlock(); locked_ = false; } inline bool RWLock::WriterLock::locked() const { return locked_; } /**** Glib::Private ********************************************************/ // static template void Private::delete_ptr(void* data) { delete static_cast(data); } template inline Private::Private(typename Private::DestructorFunc destructor_func) { // gobject_ = G_PRIVATE_INIT(destructor_func); // does not compile with --enable-warnings=fatal. // GPrivate is a struct, and G_PRIVATE_INIT is an initializer of type { ... }. // G_PRIVATE_INIT can be used only in initializations. const GPrivate temp = G_PRIVATE_INIT(destructor_func); gobject_ = temp; } template inline T* Private::get() { return static_cast(g_private_get(&gobject_)); } template inline void Private::set(T* data) { g_private_set(&gobject_, data); } template inline void Private::replace(T* data) { g_private_replace(&gobject_, data); } #endif /* DOXYGEN_SHOULD_SKIP_THIS */ // For some reason gmmproc thinks that g_iconv should be wrapped here. _IGNORE(g_iconv) } //namespace Threads } // namespace Glib